From 8bbbec9ba4462e2fea82cc27ddd33a2530145dc5 Mon Sep 17 00:00:00 2001 From: WH <1280734729@qq.com> Date: Tue, 26 Aug 2025 15:00:20 +0800 Subject: [PATCH 1/4] feat(av-cliper): add actable prop for VisibleSprite --- packages/av-canvas/src/sprites/sprite-manager.ts | 3 ++- packages/av-cliper/src/sprite/visible-sprite.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/av-canvas/src/sprites/sprite-manager.ts b/packages/av-canvas/src/sprites/sprite-manager.ts index f31a387a..fc7e827b 100644 --- a/packages/av-canvas/src/sprites/sprite-manager.ts +++ b/packages/av-canvas/src/sprites/sprite-manager.ts @@ -23,6 +23,7 @@ export class SpriteManager { } set activeSprite(s: VisibleSprite | null) { if (s === this.#activeSprite) return; + if (s && !s.actable) return; this.#activeSprite = s; this.#evtTool.emit(ESpriteManagerEvt.ActiveSpriteChange, s); } @@ -32,7 +33,7 @@ export class SpriteManager { this.getSprites() // 排在后面的层级更高 .reverse() - .find((s) => s.visible && s.rect.checkHit(x, y)) ?? null; + .find((s) => s.visible && s.actable && s.rect.checkHit(x, y)) ?? null; } async addSprite(vs: VisibleSprite): Promise { diff --git a/packages/av-cliper/src/sprite/visible-sprite.ts b/packages/av-cliper/src/sprite/visible-sprite.ts index a53e8d4d..60937503 100644 --- a/packages/av-cliper/src/sprite/visible-sprite.ts +++ b/packages/av-cliper/src/sprite/visible-sprite.ts @@ -30,6 +30,11 @@ export class VisibleSprite extends BaseSprite { */ visible = true; + /** + * 元素是否可选中,用于设置元素是否响应画布的选中事件,设置为 false 时不可选中,默认为 true 可选中 + */ + actable = true; + constructor(clip: IClip) { super(); this.#clip = clip; @@ -108,6 +113,7 @@ export class VisibleSprite extends BaseSprite { super.copyStateTo(target); if (target instanceof VisibleSprite) { target.visible = this.visible; + target.actable = this.actable; } } From ed5a16999b1f2a32ebae0db9014bd07a440894e0 Mon Sep 17 00:00:00 2001 From: WH <1280734729@qq.com> Date: Mon, 1 Sep 2025 15:57:06 +0800 Subject: [PATCH 2/4] feat(av-cliper): add interactable prop for VisibleSprite --- packages/av-canvas/src/sprites/render-ctrl.ts | 8 +++-- .../av-canvas/src/sprites/sprite-manager.ts | 7 ++-- packages/av-canvas/src/sprites/sprite-op.ts | 35 ++++++++++++++----- .../av-cliper/src/sprite/visible-sprite.ts | 10 ++++-- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/packages/av-canvas/src/sprites/render-ctrl.ts b/packages/av-canvas/src/sprites/render-ctrl.ts index 71067b83..db8b1e41 100644 --- a/packages/av-canvas/src/sprites/render-ctrl.ts +++ b/packages/av-canvas/src/sprites/render-ctrl.ts @@ -43,7 +43,6 @@ export function renderCtrls( lastActSprEvtClear = s.on('propsChange', () => { syncCtrlElPos(s, cvsEl, rectEl, ctrlsEl); }); - rectEl.style.display = ''; }); return () => { @@ -113,7 +112,7 @@ function syncCtrlElPos( Object.entries(getRectCtrls(cvsEl, s.rect)).forEach(([k, { x, y, w, h }]) => { // ctrl 是相对中心点定位的 Object.assign(ctrlsEl[k as TCtrlKey].style, { - display: 'block', + display: s.interactable === 'interactive' ? 'block' : 'none', left: '50%', top: '50%', width: `${w * cvsRatio.w}px`, @@ -122,4 +121,9 @@ function syncCtrlElPos( transform: `translate(${x * cvsRatio.w}px, ${y * cvsRatio.h}px)`, }); }); + 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 fc7e827b..e00d06e0 100644 --- a/packages/av-canvas/src/sprites/sprite-manager.ts +++ b/packages/av-canvas/src/sprites/sprite-manager.ts @@ -23,7 +23,7 @@ export class SpriteManager { } set activeSprite(s: VisibleSprite | null) { if (s === this.#activeSprite) return; - if (s && !s.actable) return; + if (s && s.interactable === 'disabled') return; this.#activeSprite = s; this.#evtTool.emit(ESpriteManagerEvt.ActiveSpriteChange, s); } @@ -33,7 +33,10 @@ export class SpriteManager { this.getSprites() // 排在后面的层级更高 .reverse() - .find((s) => s.visible && s.actable && 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 60937503..524d9596 100644 --- a/packages/av-cliper/src/sprite/visible-sprite.ts +++ b/packages/av-cliper/src/sprite/visible-sprite.ts @@ -31,9 +31,13 @@ export class VisibleSprite extends BaseSprite { visible = true; /** - * 元素是否可选中,用于设置元素是否响应画布的选中事件,设置为 false 时不可选中,默认为 true 可选中 + * 控制 Sprite 的交互状态 + * - 'interactive': 可选中,可进行移动、缩放、旋转等所有交互 + * - 'selectable': 仅可选中,但不可进行移动、缩放、旋转等交互 + * - 'disabled': 不可选中,也不可交互 + * @default 'interactive' */ - actable = true; + interactable: 'interactive' | 'selectable' | 'disabled' = 'interactive'; constructor(clip: IClip) { super(); @@ -113,7 +117,7 @@ export class VisibleSprite extends BaseSprite { super.copyStateTo(target); if (target instanceof VisibleSprite) { target.visible = this.visible; - target.actable = this.actable; + target.interactable = this.interactable; } } From a7aa9938069b04f625968754b7cfaf5aeb3c5487 Mon Sep 17 00:00:00 2001 From: WH <1280734729@qq.com> Date: Tue, 2 Sep 2025 10:31:16 +0800 Subject: [PATCH 3/4] feat(av-cliper): add interactable prop for VisibleSprite --- packages/av-canvas/src/sprites/render-ctrl.ts | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/av-canvas/src/sprites/render-ctrl.ts b/packages/av-canvas/src/sprites/render-ctrl.ts index db8b1e41..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, @@ -75,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; @@ -111,15 +118,44 @@ function syncCtrlElPos( }); Object.entries(getRectCtrls(cvsEl, s.rect)).forEach(([k, { x, y, w, h }]) => { // ctrl 是相对中心点定位的 - Object.assign(ctrlsEl[k as TCtrlKey].style, { - display: s.interactable === 'interactive' ? 'block' : 'none', + 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'; From b6fb6aa5d01040755e501794263cbaeee211853a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=97=95?= Date: Sat, 6 Sep 2025 20:34:04 +0800 Subject: [PATCH 4/4] Update sprite-manager.ts --- packages/av-canvas/src/sprites/sprite-manager.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/av-canvas/src/sprites/sprite-manager.ts b/packages/av-canvas/src/sprites/sprite-manager.ts index e00d06e0..a2cbb080 100644 --- a/packages/av-canvas/src/sprites/sprite-manager.ts +++ b/packages/av-canvas/src/sprites/sprite-manager.ts @@ -22,8 +22,7 @@ export class SpriteManager { return this.#activeSprite; } set activeSprite(s: VisibleSprite | null) { - if (s === this.#activeSprite) return; - if (s && s.interactable === 'disabled') return; + if (s === this.#activeSprite || s?.interactable === 'disabled') return; this.#activeSprite = s; this.#evtTool.emit(ESpriteManagerEvt.ActiveSpriteChange, s); }