diff --git a/packages/av-canvas/src/sprites/render-ctrl.ts b/packages/av-canvas/src/sprites/render-ctrl.ts
index 71067b83..d21ddb9c 100644
--- a/packages/av-canvas/src/sprites/render-ctrl.ts
+++ b/packages/av-canvas/src/sprites/render-ctrl.ts
@@ -3,6 +3,12 @@ import { CTRL_KEYS, TCtrlKey } from '../types';
import { createEl, getCvsRatio, getRectCtrls } from '../utils';
import { ESpriteManagerEvt, SpriteManager } from './sprite-manager';
+const CloseSvg = `
+
+`;
+
export function renderCtrls(
container: HTMLElement,
cvsEl: HTMLCanvasElement,
@@ -43,7 +49,6 @@ export function renderCtrls(
lastActSprEvtClear = s.on('propsChange', () => {
syncCtrlElPos(s, cvsEl, rectEl, ctrlsEl);
});
- rectEl.style.display = '';
});
return () => {
@@ -76,7 +81,8 @@ function createRectAndCtrlEl(container: HTMLElement): {
d.style.cssText = `
display: none;
position: absolute;
- border: 1px solid #3ee; border-radius: 50%;
+ border: 1px solid #3ee;
+ border-radius: 50%;
box-sizing: border-box;
background-color: #fff;
pointer-events: auto;
@@ -112,14 +118,48 @@ function syncCtrlElPos(
});
Object.entries(getRectCtrls(cvsEl, s.rect)).forEach(([k, { x, y, w, h }]) => {
// ctrl 是相对中心点定位的
- Object.assign(ctrlsEl[k as TCtrlKey].style, {
- display: 'block',
+ const baseStyle = {
left: '50%',
top: '50%',
width: `${w * cvsRatio.w}px`,
height: `${h * cvsRatio.h}px`,
- // border 1px, 所以要 -1
transform: `translate(${x * cvsRatio.w}px, ${y * cvsRatio.h}px)`,
- });
+ };
+ ctrlsEl[k as TCtrlKey].innerHTML = '';
+ if (k === 'rotate') {
+ Object.assign(ctrlsEl[k as TCtrlKey].style, {
+ display: s.interactable === 'interactive' ? 'block' : 'none',
+ ...baseStyle,
+ });
+ } else {
+ if (s.interactable === 'interactive') {
+ Object.assign(ctrlsEl[k as TCtrlKey].style, {
+ display: 'block',
+ backgroundColor: '#fff',
+ border: '1px solid #3ee',
+ ...baseStyle,
+ });
+ } else if (s.interactable === 'selectable') {
+ Object.assign(ctrlsEl[k as TCtrlKey].style, {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'transparent',
+ border: 'none',
+ ...baseStyle,
+ });
+ ctrlsEl[k as TCtrlKey].innerHTML = CloseSvg;
+ } else {
+ Object.assign(ctrlsEl[k as TCtrlKey].style, {
+ display: 'none',
+ ...baseStyle,
+ });
+ }
+ }
});
+ if (s.interactable === 'disabled') {
+ rectEl.style.display = 'none';
+ } else {
+ rectEl.style.display = '';
+ }
}
diff --git a/packages/av-canvas/src/sprites/sprite-manager.ts b/packages/av-canvas/src/sprites/sprite-manager.ts
index f31a387a..a2cbb080 100644
--- a/packages/av-canvas/src/sprites/sprite-manager.ts
+++ b/packages/av-canvas/src/sprites/sprite-manager.ts
@@ -22,7 +22,7 @@ export class SpriteManager {
return this.#activeSprite;
}
set activeSprite(s: VisibleSprite | null) {
- if (s === this.#activeSprite) return;
+ if (s === this.#activeSprite || s?.interactable === 'disabled') return;
this.#activeSprite = s;
this.#evtTool.emit(ESpriteManagerEvt.ActiveSpriteChange, s);
}
@@ -32,7 +32,10 @@ export class SpriteManager {
this.getSprites()
// 排在后面的层级更高
.reverse()
- .find((s) => s.visible && s.rect.checkHit(x, y)) ?? null;
+ .find(
+ (s) =>
+ s.visible && s.interactable !== 'disabled' && s.rect.checkHit(x, y),
+ ) ?? null;
}
async addSprite(vs: VisibleSprite): Promise {
diff --git a/packages/av-canvas/src/sprites/sprite-op.ts b/packages/av-canvas/src/sprites/sprite-op.ts
index 59d5f8bb..d544b06a 100644
--- a/packages/av-canvas/src/sprites/sprite-op.ts
+++ b/packages/av-canvas/src/sprites/sprite-op.ts
@@ -51,9 +51,14 @@ export function draggabelSprite(
// 移动sprite的处理函数
const onRectMouseDown = (evt: MouseEvent): void => {
- if (evt.button !== 0 || sprMng.activeSprite == null) return;
-
const hitSpr = sprMng.activeSprite;
+ if (
+ evt.button !== 0 ||
+ hitSpr == null ||
+ hitSpr.interactable !== 'interactive'
+ )
+ return;
+
const { clientX, clientY } = evt;
startRect = hitSpr.rect.clone();
@@ -69,16 +74,22 @@ export function draggabelSprite(
const cvsRatio = getCvsRatio(cvsEl);
const onMouseMove = (evt: MouseEvent): void => {
- if (sprMng.activeSprite == null || startRect == null) return;
+ const hitSpr = sprMng.activeSprite;
+ if (
+ hitSpr == null ||
+ hitSpr.interactable !== 'interactive' ||
+ startRect == null
+ )
+ return;
const { clientX, clientY } = evt;
let expectX = startRect.x + (clientX - startX) / cvsRatio.w;
let expectY = startRect.y + (clientY - startY) / cvsRatio.h;
updateRectWithSafeMargin(
- sprMng.activeSprite.rect,
+ hitSpr.rect,
cvsEl,
- refline.magneticEffect(expectX, expectY, sprMng.activeSprite.rect),
+ refline.magneticEffect(expectX, expectY, hitSpr.rect),
);
};
@@ -116,18 +127,24 @@ function setupCtrlEvents(
ctrlElements.forEach((ctrlEl, index) => {
const ctrlKey = CTRL_KEYS[index];
ctrlEl.addEventListener('pointerdown', (evt: MouseEvent) => {
- if (evt.button !== 0 || sprMng.activeSprite == null) return;
+ const hitSpr = sprMng.activeSprite;
+ if (
+ evt.button !== 0 ||
+ hitSpr == null ||
+ hitSpr.interactable !== 'interactive'
+ )
+ return;
const { clientX, clientY } = evt;
if (ctrlKey === 'rotate') {
rotateRect(
- sprMng.activeSprite.rect,
- cntMap2Outer(sprMng.activeSprite.rect.center, cvsRatio, cvsEl),
+ hitSpr.rect,
+ cntMap2Outer(hitSpr.rect.center, cvsRatio, cvsEl),
);
} else {
scaleRect({
- sprRect: sprMng.activeSprite.rect,
+ sprRect: hitSpr.rect,
ctrlKey,
startX: clientX,
startY: clientY,
diff --git a/packages/av-cliper/src/sprite/visible-sprite.ts b/packages/av-cliper/src/sprite/visible-sprite.ts
index a53e8d4d..524d9596 100644
--- a/packages/av-cliper/src/sprite/visible-sprite.ts
+++ b/packages/av-cliper/src/sprite/visible-sprite.ts
@@ -30,6 +30,15 @@ export class VisibleSprite extends BaseSprite {
*/
visible = true;
+ /**
+ * 控制 Sprite 的交互状态
+ * - 'interactive': 可选中,可进行移动、缩放、旋转等所有交互
+ * - 'selectable': 仅可选中,但不可进行移动、缩放、旋转等交互
+ * - 'disabled': 不可选中,也不可交互
+ * @default 'interactive'
+ */
+ interactable: 'interactive' | 'selectable' | 'disabled' = 'interactive';
+
constructor(clip: IClip) {
super();
this.#clip = clip;
@@ -108,6 +117,7 @@ export class VisibleSprite extends BaseSprite {
super.copyStateTo(target);
if (target instanceof VisibleSprite) {
target.visible = this.visible;
+ target.interactable = this.interactable;
}
}