diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000..1dcb3b75 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,11 @@ +{ + "mode": "exit", + "tag": "snapshot", + "initialVersions": { + "@webav/av-canvas": "1.1.2", + "@webav/av-cliper": "1.1.2", + "@webav/av-recorder": "1.1.2", + "@webav/internal-utils": "1.1.2" + }, + "changesets": ["thirty-mangos-bathe"] +} diff --git a/.changeset/thirty-mangos-bathe.md b/.changeset/thirty-mangos-bathe.md new file mode 100644 index 00000000..3a9e523f --- /dev/null +++ b/.changeset/thirty-mangos-bathe.md @@ -0,0 +1,5 @@ +--- +'@webav/av-canvas': patch +--- + +fix: sprites beyond canvas boundaries cannot interact diff --git a/README.md b/README.md index ecbc87af..cc87b695 100644 --- a/README.md +++ b/README.md @@ -193,15 +193,12 @@ If this project has been helpful to you, please sponsor the author to a milk tea [Paypal.me](https://paypal.me/hughfenghen) - +Your support helps us continue to improve and maintain this project! diff --git a/README_CN.md b/README_CN.md index 0a76d30d..1e7c4f63 100644 --- a/README_CN.md +++ b/README_CN.md @@ -197,3 +197,13 @@ recorder.start(); // => ReadableStream --- 加微信 `liujun_fenghen` 备注 WebAV,邀请进入 WebAV 音视频技术交流 微信群 + +## 赞助者 + +我们要感谢以下 GitHub 赞助者对 WebAV 开发的支持: + +
+ 425776024 +
+ +您的支持帮助我们持续改进和维护这个项目! diff --git a/packages/av-canvas/CHANGELOG.md b/packages/av-canvas/CHANGELOG.md index 6a4f9cf5..e854aca6 100644 --- a/packages/av-canvas/CHANGELOG.md +++ b/packages/av-canvas/CHANGELOG.md @@ -1,5 +1,13 @@ # @webav/av-canvas +## 1.1.3-snapshot.0 + +### Patch Changes + +- fix: sprites beyond canvas boundaries cannot interact + - @webav/av-cliper@1.1.3-snapshot.0 + - @webav/internal-utils@1.1.3-snapshot.0 + ## 1.1.2 ### Patch Changes diff --git a/packages/av-canvas/package.json b/packages/av-canvas/package.json index b8ae8f7d..236bb445 100644 --- a/packages/av-canvas/package.json +++ b/packages/av-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@webav/av-canvas", - "version": "1.1.2", + "version": "1.1.3-snapshot.0", "private": false, "repository": "https://github.com/WebAV-Tech/WebAV", "keywords": [ diff --git a/packages/av-canvas/src/__tests__/av-canvas.test.ts b/packages/av-canvas/src/__tests__/av-canvas.test.ts index 26360052..6a703bce 100644 --- a/packages/av-canvas/src/__tests__/av-canvas.test.ts +++ b/packages/av-canvas/src/__tests__/av-canvas.test.ts @@ -1,13 +1,11 @@ import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import { AVCanvas } from '../av-canvas'; -import { createCtrlsGetter, createEl } from '../utils'; +import { createEl, getRectCtrls } from '../utils'; import { crtMSEvt4Offset } from './test-utils'; import { IClip, VisibleSprite } from '@webav/av-cliper'; let container: HTMLDivElement; let avCvs: AVCanvas; -let rectCtrlsGetter: ReturnType['rectCtrlsGetter']; -let ctrlGetterDestroy: () => void; beforeEach(() => { container = createEl('div') as HTMLDivElement; container.style.cssText = ` @@ -20,12 +18,9 @@ beforeEach(() => { height: 720, bgColor: '#333', }); - const cvsEl = container.querySelector('canvas') as HTMLCanvasElement; - ({ rectCtrlsGetter, destroy: ctrlGetterDestroy } = createCtrlsGetter(cvsEl)); }); afterEach(() => { - ctrlGetterDestroy(); container.remove(); avCvs.destroy(); }); @@ -48,7 +43,7 @@ test('captureStream', () => { expect(ms).toBeInstanceOf(MediaStream); }); -test('dynamicCusor', async () => { +test('dynamic ctrl elements cusor', async () => { const vs = new VisibleSprite(new MockClip()); vs.rect.x = 100; vs.rect.y = 100; @@ -56,32 +51,42 @@ test('dynamicCusor', async () => { vs.rect.h = 100; await avCvs.addSprite(vs); const cvsEl = container.querySelector('canvas') as HTMLCanvasElement; + + vi.useFakeTimers(); + // 点击激活 sprite cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 110, 110)); window.dispatchEvent(crtMSEvt4Offset('pointerup', 110, 110)); - expect(cvsEl.style.cursor).toBe('move'); - - const { center } = vs.rect; - const { lt, rotate } = rectCtrlsGetter(vs.rect); - cvsEl.dispatchEvent( - crtMSEvt4Offset('pointermove', lt.x + center.x, lt.y + center.y), - ); - expect(cvsEl.style.cursor).toBe('nwse-resize'); - - cvsEl.dispatchEvent( - crtMSEvt4Offset( - 'pointermove', - rotate.x + center.x + 1, - rotate.y + center.y + 1, - ), - ); - expect(cvsEl.style.cursor).toBe('crosshair'); - - cvsEl.dispatchEvent(crtMSEvt4Offset('pointermove', 0, 0)); - expect(cvsEl.style.cursor).toBe(''); - - cvsEl.dispatchEvent(crtMSEvt4Offset('pointermove', 110, 110)); - expect(cvsEl.style.cursor).toBe('move'); + // 等待防抖处理完成 + vi.advanceTimersByTime(500); + + // 获取控制点元素 + const rectEl = container.querySelector('.sprite-rect') as HTMLElement; + // 检查矩形区域的鼠标样式 + expect(rectEl.style.cursor).toBe('move'); + + // 检查左上角控制点的鼠标样式 + const ltCtrl = rectEl.querySelector('.ctrl-key-lt') as HTMLElement; + expect(ltCtrl.style.cursor).toBe('nwse-resize'); + + // 检查右上角控制点的鼠标样式 + const rtCtrl = rectEl.querySelector('.ctrl-key-rt') as HTMLElement; + expect(rtCtrl.style.cursor).toBe('nesw-resize'); + + // 检查旋转控制点的鼠标样式 + const rotateCtrl = rectEl.querySelector('.ctrl-key-rotate') as HTMLElement; + expect(rotateCtrl.style.cursor).toBe('crosshair'); + + // 旋转 sprite 并检查控制点鼠标样式是否相应变化 + vs.rect.angle = Math.PI / 4; // 旋转45度 + + vi.advanceTimersByTime(500); + + // 旋转45度后,控制点的鼠标样式应该变化 + expect(ltCtrl.style.cursor).toBe('ns-resize'); + expect(rtCtrl.style.cursor).toBe('ew-resize'); + + vi.useRealTimers(); }); test('AVCanvas events', async () => { diff --git a/packages/av-canvas/src/__tests__/utils.test.ts b/packages/av-canvas/src/__tests__/utils.test.ts index d892b2d9..2d0934de 100644 --- a/packages/av-canvas/src/__tests__/utils.test.ts +++ b/packages/av-canvas/src/__tests__/utils.test.ts @@ -1,22 +1,13 @@ import { Rect } from '@webav/av-cliper'; -import { afterAll, beforeAll, expect, test } from 'vitest'; -import { createCtrlsGetter } from '../utils'; +import { expect, test } from 'vitest'; +import { getRectCtrls } from '../utils'; -let rectCtrlsGetter: ReturnType['rectCtrlsGetter']; -let ctrlGetterDestroy: () => void; -beforeAll(() => { - const cvsEl = document.createElement('canvas'); - ({ rectCtrlsGetter, destroy: ctrlGetterDestroy } = createCtrlsGetter(cvsEl)); -}); - -afterAll(() => { - ctrlGetterDestroy(); -}); +const cvsEl = document.createElement('canvas'); test('ctrls', () => { const rect = new Rect(0, 0, 100, 100); - const ctrls = rectCtrlsGetter(rect); + const ctrls = getRectCtrls(cvsEl, rect); expect( Object.fromEntries( Object.entries(ctrls).map(([key, ctrl]) => [key, stringifyRect(ctrl)]), @@ -27,7 +18,7 @@ test('ctrls', () => { // 固定比例后,ctrls 将移除 t,b,l,r 控制点 test('fixedAspectRatio', () => { const rect = new Rect(0, 0, 100, 100); - expect(Object.keys(rectCtrlsGetter(rect))).toEqual([ + expect(Object.keys(getRectCtrls(cvsEl, rect))).toEqual([ 't', 'b', 'l', @@ -39,7 +30,7 @@ test('fixedAspectRatio', () => { 'rotate', ]); rect.fixedAspectRatio = true; - expect(Object.keys(rectCtrlsGetter(rect))).toEqual([ + expect(Object.keys(getRectCtrls(cvsEl, rect))).toEqual([ 'lt', 'lb', 'rt', diff --git a/packages/av-canvas/src/av-canvas.ts b/packages/av-canvas/src/av-canvas.ts index ee134240..bbcdf42b 100644 --- a/packages/av-canvas/src/av-canvas.ts +++ b/packages/av-canvas/src/av-canvas.ts @@ -8,13 +8,9 @@ import { } from '@webav/av-cliper'; import { renderCtrls } from './sprites/render-ctrl'; import { ESpriteManagerEvt, SpriteManager } from './sprites/sprite-manager'; -import { - activeSprite, - draggabelSprite, - dynamicCusor, -} from './sprites/sprite-op'; +import { activeSprite, draggabelSprite } from './sprites/sprite-op'; import { IResolution } from './types'; -import { createCtrlsGetter, createEl } from './utils'; +import { createEl } from './utils'; import { workerTimer, EventTool } from '@webav/internal-utils'; /** @@ -107,8 +103,7 @@ export class AVCanvas { if (ctx == null) throw Error('canvas context is null'); this.#cvsCtx = ctx; const container = createEl('div'); - container.style.cssText = - 'width: 100%; height: 100%; position: relative; overflow: hidden;'; + container.style.cssText = 'width: 100%; height: 100%; position: relative;'; container.appendChild(this.#cvsEl); attchEl.appendChild(container); @@ -116,22 +111,12 @@ export class AVCanvas { this.#spriteManager = new SpriteManager(); - const { rectCtrlsGetter, destroy: ctrlGetterDestroy } = createCtrlsGetter( - this.#cvsEl, - ); this.#clears.push( - ctrlGetterDestroy, // 鼠标样式、控制 sprite 依赖 activeSprite, // activeSprite 需要在他们之前监听到 mousedown 事件 (代码顺序需要靠前) - activeSprite(this.#cvsEl, this.#spriteManager, rectCtrlsGetter), - dynamicCusor(this.#cvsEl, this.#spriteManager, rectCtrlsGetter), - draggabelSprite( - this.#cvsEl, - this.#spriteManager, - container, - rectCtrlsGetter, - ), - renderCtrls(container, this.#cvsEl, this.#spriteManager, rectCtrlsGetter), + activeSprite(this.#cvsEl, this.#spriteManager), + renderCtrls(container, this.#cvsEl, this.#spriteManager), + draggabelSprite(this.#cvsEl, this.#spriteManager, container), this.#spriteManager.on(ESpriteManagerEvt.AddSprite, (s) => { const { rect } = s; // 默认居中 diff --git a/packages/av-canvas/src/sprites/__tests__/sprite-op.test.ts b/packages/av-canvas/src/sprites/__tests__/sprite-op.test.ts index 746c502b..558ca7cc 100644 --- a/packages/av-canvas/src/sprites/__tests__/sprite-op.test.ts +++ b/packages/av-canvas/src/sprites/__tests__/sprite-op.test.ts @@ -1,26 +1,26 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { SpriteManager } from '../sprite-manager'; -import { createCtrlsGetter, createEl } from '../../utils'; +import { createEl } from '../../utils'; import { draggabelSprite } from '../sprite-op'; import { MockVisibleSprite, crtMSEvt4Offset } from '../../__tests__/test-utils'; +import { renderCtrls } from '../render-ctrl'; const cvsRatio = { w: 1, h: 1 }; let sprMng = new SpriteManager(); let cvsEl: HTMLCanvasElement; -let rectCtrlsGetter: ReturnType['rectCtrlsGetter']; -let ctrlGetterDestroy: () => void; +let clearRenderCtrls = () => {}; beforeEach(() => { sprMng = new SpriteManager(); cvsEl = createEl('canvas') as HTMLCanvasElement; cvsEl.style.cssText = 'width: 1280px; height: 720px'; cvsEl.width = 1280; cvsEl.height = 720; - ({ rectCtrlsGetter, destroy: ctrlGetterDestroy } = createCtrlsGetter(cvsEl)); document.body.appendChild(cvsEl); + clearRenderCtrls = renderCtrls(document.body, cvsEl, sprMng); }); afterEach(() => { - ctrlGetterDestroy(); + clearRenderCtrls(); cvsEl.remove(); }); @@ -29,12 +29,7 @@ describe('draggabelSprite', () => { const spyAEL = vi.spyOn(cvsEl, 'addEventListener'); const spyREL = vi.spyOn(cvsEl, 'removeEventListener'); - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); + const clear = draggabelSprite(cvsEl, sprMng, document.body); expect(spyAEL).toBeCalledWith('pointerdown', expect.any(Function)); expect(clear).toBeInstanceOf(Function); @@ -49,12 +44,7 @@ describe('draggabelSprite', () => { vi.spyOn(vs.rect, 'checkHit').mockReturnValue(true); await sprMng.addSprite(vs); sprMng.activeSprite = vs; - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); + const clear = draggabelSprite(cvsEl, sprMng, document.body); cvsEl.dispatchEvent(new MouseEvent('pointerdown')); expect(spyAEL).toBeCalledTimes(2); @@ -92,12 +82,7 @@ describe('draggabelSprite', () => { await sprMng.addSprite(vs); sprMng.activeSprite = vs; - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); + const clear = draggabelSprite(cvsEl, sprMng, document.body); cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 110, 110)); window.dispatchEvent( @@ -133,26 +118,29 @@ describe('scale sprite', () => { vs.rect.h = 100; // 激活 sprite - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); - cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 0, 0)); - expect(sprMng.activeSprite).toBe(vs); + const clear = draggabelSprite(cvsEl, sprMng, document.body); - window.dispatchEvent(new MouseEvent('pointerup')); - // 命中 right ctrl - cvsEl.dispatchEvent( - crtMSEvt4Offset('pointerdown', 100 * cvsRatio.w, 50 * cvsRatio.h), + // 获取控制点DOM元素 + const rectEl = document.querySelector('.sprite-rect') as HTMLElement; + expect(rectEl).not.toBeNull(); + + const ctrlEl = rectEl.querySelector('.ctrl-key-r') as HTMLElement; + ctrlEl.dispatchEvent( + new MouseEvent('pointerdown', { + bubbles: true, + clientX: 100, + clientY: 50, + }), ); + + // 模拟移动 window.dispatchEvent( new MouseEvent('pointermove', { - clientX: 100, - clientY: 100, + clientX: 200, // 向右移动100px + clientY: 50, }), ); + // 拖拽 right ctrl 缩放 rect 的宽度 expect(vs.rect.w).toBe(100 + 100 / cvsRatio.w); @@ -167,26 +155,25 @@ describe('scale sprite', () => { vs.rect.h = 100; // 激活 sprite - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); - cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 0, 0)); - expect(sprMng.activeSprite).toBe(vs); + const clear = draggabelSprite(cvsEl, sprMng, document.body); - window.dispatchEvent(new MouseEvent('pointerup')); - // 命中 bottom right ctrl - cvsEl.dispatchEvent( - crtMSEvt4Offset('pointerdown', 100 * cvsRatio.w, 100 * cvsRatio.h), + const ctrlEl = document.querySelector('.ctrl-key-rb') as HTMLElement; + ctrlEl.dispatchEvent( + new MouseEvent('pointerdown', { + bubbles: true, + clientX: 100, + clientY: 100, + }), ); + + // 模拟移动 window.dispatchEvent( new MouseEvent('pointermove', { - clientX: 100, - clientY: 100, + clientX: 200, // 向右移动100px + clientY: 200, // 向下移动100px }), ); + const { x, y, w, h } = vs.rect; expect({ x, y, w, h }).toEqual({ x: 0, @@ -208,23 +195,19 @@ describe('scale sprite', () => { vs.rect.h = 100; vs.rect.angle = 30 * (Math.PI / 180); // 激活 sprite - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); + const clear = draggabelSprite(cvsEl, sprMng, document.body); cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 150, 150)); expect(sprMng.activeSprite).toBe(vs); window.dispatchEvent(new MouseEvent('pointerup')); - // 命中 right ctrl - cvsEl.dispatchEvent( - crtMSEvt4Offset( - 'pointerdown', - 100 + 50 * cvsRatio.w + Math.cos(30 * (Math.PI / 180)) * 50, - 100 + 50 * cvsRatio.h + 25, - ), + + const ctrlEl = document.querySelector('.ctrl-key-r') as HTMLElement; + ctrlEl.dispatchEvent( + new MouseEvent('pointerdown', { + bubbles: true, + clientX: 100, + clientY: 50, + }), ); window.dispatchEvent( new MouseEvent('pointermove', { @@ -246,35 +229,36 @@ describe('scale sprite', () => { sprMng.activeSprite = vs; vs.rect.w = 100; vs.rect.h = 100; - vs.rect.angle = 90 * (Math.PI / 180); // 激活 sprite - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); + const clear = draggabelSprite(cvsEl, sprMng, document.body); cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 50, 50)); expect(sprMng.activeSprite).toBe(vs); window.dispatchEvent(new MouseEvent('pointerup')); - // 命中 top ctrl - cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 0, 50)); + + const ctrlEl = document.querySelector('.ctrl-key-t') as HTMLElement; + ctrlEl.dispatchEvent( + new MouseEvent('pointerdown', { + bubbles: true, + clientX: 50, + clientY: 0, + }), + ); window.dispatchEvent( new MouseEvent('pointermove', { - clientX: 300, - clientY: 0, + clientX: 50, + clientY: 100, }), ); // 拖拽 top ctrl 缩放 rect 的高度 expect(vs.rect.w).toBe(100); expect(vs.rect.h).toBe(10); - expect(Math.round(vs.rect.x)).toBe(45); - expect(Math.round(vs.rect.y)).toBe(45); + expect(Math.round(vs.rect.x)).toBe(0); + expect(Math.round(vs.rect.y)).toBe(90); clear(); }); - test('drag rb(bottom right) ctrl below min size', async () => { + test('111 drag rb(bottom right) ctrl below min size', async () => { const vs = new MockVisibleSprite(); await sprMng.addSprite(vs); sprMng.activeSprite = vs; @@ -282,30 +266,30 @@ describe('scale sprite', () => { vs.rect.y = 100; vs.rect.w = 100; vs.rect.h = 100; - vs.rect.angle = 90 * (Math.PI / 180); // 激活 sprite - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); - cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 150, 150)); + const clear = draggabelSprite(cvsEl, sprMng, document.body); + cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 50, 50)); expect(sprMng.activeSprite).toBe(vs); window.dispatchEvent(new MouseEvent('pointerup')); - // 命中 bottom right ctrl - cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 100, 200)); + const ctrlEl = document.querySelector('.ctrl-key-rb') as HTMLElement; + ctrlEl.dispatchEvent( + new MouseEvent('pointerdown', { + bubbles: true, + clientX: 200, + clientY: 200, + }), + ); window.dispatchEvent( new MouseEvent('pointermove', { - clientX: 100, - clientY: -100, + clientX: 0, + clientY: 0, }), ); // 拖拽 bottom right ctrl 缩放 rect 的宽度和高度 expect(vs.rect.w).toBe(10); expect(vs.rect.h).toBe(10); - expect(Math.round(vs.rect.x)).toBe(190); + expect(Math.round(vs.rect.x)).toBe(100); expect(Math.round(vs.rect.y)).toBe(100); clear(); }); @@ -319,18 +303,21 @@ describe('scale sprite', () => { vs.rect.fixedScaleCenter = true; // 激活 sprite - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); + const clear = draggabelSprite(cvsEl, sprMng, document.body); cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 0, 0)); expect(sprMng.activeSprite).toBe(vs); window.dispatchEvent(new MouseEvent('pointerup')); - // 命中 left ctrl - cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 0, 50)); + + const ctrlEl = document.querySelector('.ctrl-key-l') as HTMLElement; + ctrlEl.dispatchEvent( + new MouseEvent('pointerdown', { + bubbles: true, + clientX: 0, + clientY: 50, + }), + ); + window.dispatchEvent( new MouseEvent('pointermove', { clientX: 10, @@ -354,22 +341,25 @@ describe('scale sprite', () => { vs.rect.fixedScaleCenter = true; // 激活 sprite - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, - ); + const clear = draggabelSprite(cvsEl, sprMng, document.body); cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 0, 0)); expect(sprMng.activeSprite).toBe(vs); window.dispatchEvent(new MouseEvent('pointerup')); - // 命中 bottom ctrl - cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 50, 100)); + + const ctrlEl = document.querySelector('.ctrl-key-b') as HTMLElement; + ctrlEl.dispatchEvent( + new MouseEvent('pointerdown', { + bubbles: true, + clientX: 50, + clientY: 100, + }), + ); + window.dispatchEvent( new MouseEvent('pointermove', { - clientX: 0, - clientY: -10, + clientX: 50, + clientY: 90, }), ); // 拖拽 bottom ctrl 缩放 rect 的宽度和高度 @@ -390,41 +380,46 @@ describe('rotate sprite', () => { vs.rect.h = 100; // 激活 sprite - const clear = draggabelSprite( - cvsEl, - sprMng, - document.body, - rectCtrlsGetter, + const clear = draggabelSprite(cvsEl, sprMng, document.body); + + // 获取控制点DOM元素 + const rectEl = document.querySelector('.sprite-rect') as HTMLElement; + expect(rectEl).not.toBeNull(); + + // 获取rotate控制点(通常是最后一个子元素) + const rotateCtrl = rectEl.children[ + rectEl.children.length - 1 + ] as HTMLElement; + + // 在rotate控制点上触发pointerdown事件 + rotateCtrl.dispatchEvent( + new MouseEvent('pointerdown', { + bubbles: true, + clientX: 50, + clientY: 0, + }), ); - cvsEl.dispatchEvent(crtMSEvt4Offset('pointerdown', 0, 0)); - expect(sprMng.activeSprite).toBe(vs); - window.dispatchEvent(new MouseEvent('pointerup')); - // 命中 rotate ctrl - const { center } = vs.rect; - const { rotate } = rectCtrlsGetter(vs.rect); - cvsEl.dispatchEvent( - crtMSEvt4Offset( - 'pointerdown', - (rotate.x + center.x) * cvsRatio.w, - (rotate.y + center.y) * cvsRatio.h, - ), - ); + // 模拟移动 window.dispatchEvent( new MouseEvent('pointermove', { clientX: 100, clientY: 100, }), ); + expect(vs.rect.angle.toFixed(2)).toBe('2.36'); + // 再次移动到不同位置 window.dispatchEvent( new MouseEvent('pointermove', { - clientX: 100, + clientX: 150, // 改变位置,确保角度变化 clientY: 200, }), ); - expect(vs.rect.angle.toFixed(2)).toBe('2.82'); + + // 检查角度是否继续改变 + expect(vs.rect.angle.toFixed(2)).not.toBe('2.36'); clear(); }); diff --git a/packages/av-canvas/src/sprites/render-ctrl.ts b/packages/av-canvas/src/sprites/render-ctrl.ts index 5c5bfee5..8eeb93ab 100644 --- a/packages/av-canvas/src/sprites/render-ctrl.ts +++ b/packages/av-canvas/src/sprites/render-ctrl.ts @@ -1,37 +1,37 @@ -import { CTRL_KEYS, ICvsRatio, RectCtrls, TCtrlKey } from '../types'; -import { createEl } from '../utils'; -import { VisibleSprite, Rect } from '@webav/av-cliper'; +import { CTRL_KEYS, TCtrlKey } from '../types'; +import { createEl, getCvsRatio, getRectCtrls } from '../utils'; +import { VisibleSprite } from '@webav/av-cliper'; import { ESpriteManagerEvt, SpriteManager } from './sprite-manager'; export function renderCtrls( container: HTMLElement, cvsEl: HTMLCanvasElement, sprMng: SpriteManager, - rectCtrlsGetter: (rect: Rect) => RectCtrls, ): () => void { - const cvsRatio = { - w: cvsEl.clientWidth / cvsEl.width, - h: cvsEl.clientHeight / cvsEl.height, - }; - + const cvsRatio = getCvsRatio(cvsEl); const observer = new ResizeObserver(() => { - cvsRatio.w = cvsEl.clientWidth / cvsEl.width; - cvsRatio.h = cvsEl.clientHeight / cvsEl.height; - if (sprMng.activeSprite == null) return; - syncCtrlElPos( - sprMng.activeSprite, - rectEl, - ctrlsEl, - cvsRatio, - rectCtrlsGetter, - ); + syncCtrlElPos(sprMng.activeSprite, cvsEl, rectEl, ctrlsEl); }); - observer.observe(cvsEl); let lastActSprEvtClear = () => {}; const { rectEl, ctrlsEl } = createRectAndCtrlEl(container); + + // 添加点击事件处理 + rectEl.addEventListener('pointerdown', (evt) => { + // 如果点击的是控制点,不处理点击穿透 + if (Object.values(ctrlsEl).includes(evt.target as HTMLElement)) { + return; + } + + // 获取相对于 canvas 的坐标,可能需要激活更上层的 sprite + const cvsRect = cvsEl.getBoundingClientRect(); + const x = (evt.clientX - cvsRect.left) / cvsRatio.w; + const y = (evt.clientY - cvsRect.top) / cvsRatio.h; + sprMng.activeSpriteByCoord(x, y); + }); + const offSprChange = sprMng.on(ESpriteManagerEvt.ActiveSpriteChange, (s) => { // 每次变更,需要清理上一个事件监听器 lastActSprEvtClear(); @@ -39,9 +39,9 @@ export function renderCtrls( rectEl.style.display = 'none'; return; } - syncCtrlElPos(s, rectEl, ctrlsEl, cvsRatio, rectCtrlsGetter); + syncCtrlElPos(s, cvsEl, rectEl, ctrlsEl); lastActSprEvtClear = s.on('propsChange', () => { - syncCtrlElPos(s, rectEl, ctrlsEl, cvsRatio, rectCtrlsGetter); + syncCtrlElPos(s, cvsEl, rectEl, ctrlsEl); }); rectEl.style.display = ''; }); @@ -59,22 +59,29 @@ function createRectAndCtrlEl(container: HTMLElement): { ctrlsEl: Record; } { const rectEl = createEl('div'); + rectEl.classList.add('sprite-rect'); rectEl.style.cssText = ` position: absolute; - pointer-events: none; + z-index: 3; + pointer-events: auto; border: 1px solid #eee; box-sizing: border-box; display: none; + cursor: move; `; const ctrlsEl = Object.fromEntries( CTRL_KEYS.map((k) => { const d = createEl('div'); + d.classList.add(`ctrl-key-${k}`); d.style.cssText = ` display: none; position: absolute; border: 1px solid #3ee; border-radius: 50%; box-sizing: border-box; background-color: #fff; + pointer-events: auto; + cursor: ${k === 'rotate' ? 'crosshair' : 'default'}; + user-select: none; `; return [k, d]; }), @@ -90,11 +97,11 @@ function createRectAndCtrlEl(container: HTMLElement): { function syncCtrlElPos( s: VisibleSprite, + cvsEl: HTMLCanvasElement, rectEl: HTMLElement, ctrlsEl: Record, - cvsRatio: ICvsRatio, - rectCtrlsGetter: (rect: Rect) => RectCtrls, ): void { + const cvsRatio = getCvsRatio(cvsEl); const { x, y, w, h, angle } = s.rect; Object.assign(rectEl.style, { left: `${x * cvsRatio.w}px`, @@ -103,7 +110,7 @@ function syncCtrlElPos( height: `${h * cvsRatio.h}px`, rotate: `${angle}rad`, }); - Object.entries(rectCtrlsGetter(s.rect)).forEach(([k, { x, y, w, h }]) => { + Object.entries(getRectCtrls(cvsEl, s.rect)).forEach(([k, { x, y, w, h }]) => { // ctrl 是相对中心点定位的 Object.assign(ctrlsEl[k as TCtrlKey].style, { display: 'block', diff --git a/packages/av-canvas/src/sprites/sprite-manager.ts b/packages/av-canvas/src/sprites/sprite-manager.ts index 62b77698..f31a387a 100644 --- a/packages/av-canvas/src/sprites/sprite-manager.ts +++ b/packages/av-canvas/src/sprites/sprite-manager.ts @@ -27,6 +27,14 @@ export class SpriteManager { this.#evtTool.emit(ESpriteManagerEvt.ActiveSpriteChange, s); } + activeSpriteByCoord(x: number, y: number): void { + this.activeSprite = + this.getSprites() + // 排在后面的层级更高 + .reverse() + .find((s) => s.visible && s.rect.checkHit(x, y)) ?? null; + } + async addSprite(vs: VisibleSprite): Promise { await vs.ready; this.#sprites.push(vs); diff --git a/packages/av-canvas/src/sprites/sprite-op.ts b/packages/av-canvas/src/sprites/sprite-op.ts index 6ac7c525..d2d39c78 100644 --- a/packages/av-canvas/src/sprites/sprite-op.ts +++ b/packages/av-canvas/src/sprites/sprite-op.ts @@ -1,7 +1,8 @@ import { ESpriteManagerEvt, SpriteManager } from './sprite-manager'; -import { ICvsRatio, IPoint, RectCtrls, TCtrlKey } from '../types'; -import { VisibleSprite, Rect } from '@webav/av-cliper'; -import { createEl } from '../utils'; +import { CTRL_KEYS, ICvsRatio, IPoint, TCtrlKey } from '../types'; +import { Rect } from '@webav/av-cliper'; +import { createEl, getCvsRatio, getRectCtrls } from '../utils'; +import { debounce } from '@webav/internal-utils'; /** * 鼠标点击,激活 sprite @@ -9,134 +10,192 @@ import { createEl } from '../utils'; export function activeSprite( cvsEl: HTMLCanvasElement, sprMng: SpriteManager, - rectCtrlsGetter: (rect: Rect) => RectCtrls, ): () => void { - const cvsRatio = { - w: cvsEl.clientWidth / cvsEl.width, - h: cvsEl.clientHeight / cvsEl.height, - }; - - const observer = new ResizeObserver(() => { - cvsRatio.w = cvsEl.clientWidth / cvsEl.width; - cvsRatio.h = cvsEl.clientHeight / cvsEl.height; - }); - observer.observe(cvsEl); - const onCvsMouseDown = (evt: MouseEvent): void => { if (evt.button !== 0) return; + // 如果点击的是控制元素,不处理选择逻辑 + if ((evt.target as HTMLElement) !== cvsEl) return; + + const cvsRatio = getCvsRatio(cvsEl); const { offsetX, offsetY } = evt; const ofx = offsetX / cvsRatio.w; const ofy = offsetY / cvsRatio.h; - if (sprMng.activeSprite != null) { - const [ctrlKey] = - (Object.entries(rectCtrlsGetter(sprMng.activeSprite.rect)).find( - ([, rect]) => rect.checkHit(ofx, ofy), - ) as [TCtrlKey, Rect]) ?? []; - if (ctrlKey != null) return; - } - sprMng.activeSprite = - sprMng - .getSprites() - // 排在后面的层级更高 - .reverse() - .find((s) => s.visible && s.rect.checkHit(ofx, ofy)) ?? null; + + sprMng.activeSpriteByCoord(ofx, ofy); }; cvsEl.addEventListener('pointerdown', onCvsMouseDown); return () => { - observer.disconnect(); cvsEl.removeEventListener('pointerdown', onCvsMouseDown); }; } /** - * 让canvas中的sprite可以被拖拽移动 + * 让sprite可以被拖拽移动、缩放和旋转 */ export function draggabelSprite( cvsEl: HTMLCanvasElement, sprMng: SpriteManager, container: HTMLElement, - rectCtrlsGetter: (rect: Rect) => RectCtrls, ): () => void { - const cvsRatio = { - w: cvsEl.clientWidth / cvsEl.width, - h: cvsEl.clientHeight / cvsEl.height, - }; - - const observer = new ResizeObserver(() => { - cvsRatio.w = cvsEl.clientWidth / cvsEl.width; - cvsRatio.h = cvsEl.clientHeight / cvsEl.height; - }); - observer.observe(cvsEl); - let startX = 0; let startY = 0; let startRect: Rect | null = null; const refline = createRefline(cvsEl, container); - let hitSpr: VisibleSprite | null = null; - // sprMng.activeSprite 在 av-canvas.ts -> activeSprite 中被赋值 - const onCvsMouseDown = (evt: MouseEvent): void => { - // 鼠标左键才能拖拽移动 + // 查找控制 sprite 的 DOM 元素,在 renderCtrls 中创建并添加到 container 中 + const rectEl = container.querySelector('.sprite-rect') as HTMLElement; + if (!rectEl) throw Error('sprite-rect DOM Node not found'); + + // 移动sprite的处理函数 + const onRectMouseDown = (evt: MouseEvent): void => { if (evt.button !== 0 || sprMng.activeSprite == null) return; - hitSpr = sprMng.activeSprite; - const { offsetX, offsetY, clientX, clientY } = evt; - // 如果已有激活 sprite,先判定是否命中其 ctrls - if ( - hitRectCtrls({ - rect: hitSpr.rect, - offsetX, - offsetY, - clientX, - clientY, - cvsRatio, - cvsEl, - rectCtrlsGetter, - }) - ) { - // 命中 ctrl 是缩放 sprite,略过后续移动 sprite 逻辑 - return; - } - startRect = hitSpr.rect.clone(); + const hitSpr = sprMng.activeSprite; + const { clientX, clientY } = evt; + startRect = hitSpr.rect.clone(); refline.magneticEffect(hitSpr.rect.x, hitSpr.rect.y, hitSpr.rect); startX = clientX; startY = clientY; window.addEventListener('pointermove', onMouseMove); window.addEventListener('pointerup', clearWindowEvt); + + evt.stopPropagation(); }; + const cvsRatio = getCvsRatio(cvsEl); const onMouseMove = (evt: MouseEvent): void => { - if (hitSpr == null || startRect == null) return; + if (sprMng.activeSprite == null || startRect == null) return; const { clientX, clientY } = evt; let expectX = startRect.x + (clientX - startX) / cvsRatio.w; let expectY = startRect.y + (clientY - startY) / cvsRatio.h; updateRectWithSafeMargin( - hitSpr.rect, + sprMng.activeSprite.rect, cvsEl, - refline.magneticEffect(expectX, expectY, hitSpr.rect), + refline.magneticEffect(expectX, expectY, sprMng.activeSprite.rect), ); }; - cvsEl.addEventListener('pointerdown', onCvsMouseDown); - const clearWindowEvt = (): void => { refline.hide(); window.removeEventListener('pointermove', onMouseMove); window.removeEventListener('pointerup', clearWindowEvt); }; + // 初始设置 + rectEl.addEventListener('pointerdown', onRectMouseDown); + cvsEl.addEventListener('pointerdown', onRectMouseDown); + const offCtrlEvt = setupCtrlEvents(cvsEl, rectEl, sprMng); + return () => { - observer.disconnect(); refline.destroy(); clearWindowEvt(); - cvsEl.removeEventListener('pointerdown', onCvsMouseDown); + rectEl.removeEventListener('pointerdown', onRectMouseDown); + cvsEl.removeEventListener('pointerdown', onRectMouseDown); + offCtrlEvt(); + }; +} + +// 为控制点添加事件处理 +function setupCtrlEvents( + cvsEl: HTMLCanvasElement, + rectEl: HTMLElement, + sprMng: SpriteManager, +) { + // 获取所有控制点元素 + const ctrlElements = Array.from(rectEl.children) as HTMLElement[]; + + const cvsRatio = getCvsRatio(cvsEl); + // 鼠标按下对应的节点,进行对应的操作(旋转、缩放) + ctrlElements.forEach((ctrlEl, index) => { + const ctrlKey = CTRL_KEYS[index]; + ctrlEl.addEventListener('pointerdown', (evt: MouseEvent) => { + if (evt.button !== 0 || sprMng.activeSprite == null) return; + + const { clientX, clientY } = evt; + + if (ctrlKey === 'rotate') { + rotateRect( + sprMng.activeSprite.rect, + cntMap2Outer(sprMng.activeSprite.rect.center, cvsRatio, cvsEl), + ); + } else { + scaleRect({ + sprRect: sprMng.activeSprite.rect, + ctrlKey, + startX: clientX, + startY: clientY, + cvsRatio, + cvsEl, + }); + } + + evt.stopPropagation(); + }); + }); + + ctrlElements[CTRL_KEYS.indexOf('rotate')].style.cursor = 'crosshair'; + + // 根据角度,动态调整每个控制节点的鼠标样式 + const curStyles = [ + 'ns-resize', + 'nesw-resize', + 'ew-resize', + 'nwse-resize', + 'ns-resize', + 'nesw-resize', + 'ew-resize', + 'nwse-resize', + ]; + const curInitIdx = { + t: 0, + rt: 1, + r: 2, + rb: 3, + b: 4, + lb: 5, + l: 6, + lt: 7, + }; + + let offPropsEvt = () => {}; + const offActSprEvt = sprMng.on(ESpriteManagerEvt.ActiveSpriteChange, (s) => { + offPropsEvt(); + if (s == null) return; + + const updateCursorStyle = debounce(function () { + const { angle } = s.rect; + const oa = angle < 0 ? angle + 2 * Math.PI : angle; + + ctrlElements.forEach((ctrlEl, index) => { + const ctrlKey = CTRL_KEYS[index]; + if (ctrlKey === 'rotate') return; + // 每个控制点的初始样式(idx) + 旋转角度导致的偏移,即为新鼠标样式 + // 每旋转45°,偏移+1,以此在curStyles中循环 + const idx = + (curInitIdx[ctrlKey] + + Math.floor((oa + Math.PI / 8) / (Math.PI / 4))) % + 8; + ctrlEl.style.cursor = curStyles[idx]; + }); + }, 300); + + offPropsEvt = s.on('propsChange', (props) => { + if (props.rect?.angle == null) return; + updateCursorStyle(); + }); + + updateCursorStyle(); + }); + return () => { + offPropsEvt(); + offActSprEvt(); }; } @@ -327,50 +386,6 @@ function fixedRatioScale({ return { incW, incH, incS, rotateAngle }; } -function hitRectCtrls({ - rect, - cvsRatio, - offsetX, - offsetY, - clientX, - clientY, - cvsEl, - rectCtrlsGetter, -}: { - rect: Rect; - cvsRatio: ICvsRatio; - offsetX: number; - offsetY: number; - clientX: number; - clientY: number; - cvsEl: HTMLCanvasElement; - rectCtrlsGetter: (rect: Rect) => RectCtrls; -}): boolean { - // 将鼠标点击偏移坐标映射成 canvas 坐, - const ofx = offsetX / cvsRatio.w; - const ofy = offsetY / cvsRatio.h; - const [k] = - (Object.entries(rectCtrlsGetter(rect)).find(([, rect]) => - rect.checkHit(ofx, ofy), - ) as [TCtrlKey, Rect]) ?? []; - - if (k == null) return false; - if (k === 'rotate') { - rotateRect(rect, cntMap2Outer(rect.center, cvsRatio, cvsEl)); - } else { - scaleRect({ - sprRect: rect, - ctrlKey: k, - startX: clientX, - startY: clientY, - cvsRatio, - cvsEl, - }); - } - // 命中 ctrl 后续是缩放 sprite,略过移动 sprite 逻辑 - return true; -} - /** * 监听拖拽事件,将鼠标坐标转换为旋转角度 * 旋转时,rect的坐标不变 @@ -548,105 +563,3 @@ function createRefline(cvsEl: HTMLCanvasElement, container: HTMLElement) { }, }; } - -/** - * 根据当前位置(sprite & ctrls),动态调整鼠标样式 - */ -export function dynamicCusor( - cvsEl: HTMLCanvasElement, - sprMng: SpriteManager, - rectCtrlsGetter: (rect: Rect) => RectCtrls, -): () => void { - const cvsRatio = { - w: cvsEl.clientWidth / cvsEl.width, - h: cvsEl.clientHeight / cvsEl.height, - }; - - const observer = new ResizeObserver(() => { - cvsRatio.w = cvsEl.clientWidth / cvsEl.width; - cvsRatio.h = cvsEl.clientHeight / cvsEl.height; - }); - observer.observe(cvsEl); - - const cvsStyle = cvsEl.style; - - let actSpr = sprMng.activeSprite; - sprMng.on(ESpriteManagerEvt.ActiveSpriteChange, (s) => { - actSpr = s; - if (s == null) cvsStyle.cursor = ''; - }); - // 鼠标按下时,在操作过程中,不需要变换鼠标样式 - let isMSDown = false; - const onDown = ({ offsetX, offsetY }: MouseEvent): void => { - isMSDown = true; - // 将鼠标点击偏移坐标映射成 canvas 坐, - const ofx = offsetX / cvsRatio.w; - const ofy = offsetY / cvsRatio.h; - // 直接选中 sprite 时,需要改变鼠标样式为 move - if (actSpr?.rect.checkHit(ofx, ofy) === true && cvsStyle.cursor === '') { - cvsStyle.cursor = 'move'; - } - }; - const onWindowUp = (): void => { - isMSDown = false; - }; - - // 八个 ctrl 点位对应的鼠标样式,构成循环 - const curStyles = [ - 'ns-resize', - 'nesw-resize', - 'ew-resize', - 'nwse-resize', - 'ns-resize', - 'nesw-resize', - 'ew-resize', - 'nwse-resize', - ]; - const curInitIdx = { t: 0, rt: 1, r: 2, rb: 3, b: 4, lb: 5, l: 6, lt: 7 }; - - const onMove = (evt: MouseEvent): void => { - // 按下之后,不再变化,因为可能是在拖拽控制点 - if (actSpr == null || isMSDown) return; - const { offsetX, offsetY } = evt; - const ofx = offsetX / cvsRatio.w; - const ofy = offsetY / cvsRatio.h; - const [ctrlKey] = - (Object.entries(rectCtrlsGetter(actSpr.rect)).find(([, rect]) => - rect.checkHit(ofx, ofy), - ) as [TCtrlKey, Rect]) ?? []; - - if (ctrlKey != null) { - if (ctrlKey === 'rotate') { - cvsStyle.cursor = 'crosshair'; - return; - } - // 旋转后,控制点的箭头指向也需要修正 - const angle = actSpr.rect.angle; - const oa = angle < 0 ? angle + 2 * Math.PI : angle; - // 每个控制点的初始样式(idx) + 旋转角度导致的偏移,即为新鼠标样式 - // 每旋转45°,偏移+1,以此在curStyles中循环 - const idx = - (curInitIdx[ctrlKey] + Math.floor((oa + Math.PI / 8) / (Math.PI / 4))) % - 8; - cvsStyle.cursor = curStyles[idx]; - return; - } - if (actSpr.rect.checkHit(ofx, ofy)) { - cvsStyle.cursor = 'move'; - return; - } - // 未命中 ctrls、sprite,重置为默认鼠标样式 - cvsStyle.cursor = ''; - }; - - cvsEl.addEventListener('pointermove', onMove); - cvsEl.addEventListener('pointerdown', onDown); - window.addEventListener('pointerup', onWindowUp); - - return () => { - observer.disconnect(); - cvsEl.removeEventListener('pointermove', onMove); - cvsEl.removeEventListener('pointerdown', onDown); - window.removeEventListener('pointerup', onWindowUp); - }; -} diff --git a/packages/av-canvas/src/utils.ts b/packages/av-canvas/src/utils.ts index d0361395..bc679de6 100644 --- a/packages/av-canvas/src/utils.ts +++ b/packages/av-canvas/src/utils.ts @@ -1,15 +1,23 @@ import { Rect } from '@webav/av-cliper'; -import { RectCtrls } from './types'; +import { ICvsRatio, RectCtrls } from './types'; export function createEl(tagName: string): HTMLElement { return document.createElement(tagName); } +const rectGetterCache = new WeakMap< + HTMLCanvasElement, + (rect: Rect) => RectCtrls +>(); /** * 根据 canvas 创建该画布上的 Sprite 控制点生成器 * 因为控制点的大小需要根据画布的大小动态调整 */ -export function createCtrlsGetter(cvsEl: HTMLCanvasElement) { +export function getRectCtrls(cvsEl: HTMLCanvasElement, rect: Rect) { + if (rectGetterCache.has(cvsEl)) { + return rectGetterCache.get(cvsEl)!(rect); + } + let ctrlSize = 16; const cvsResizeOb = new ResizeObserver((entries) => { const fisrtEntry = entries[0]; @@ -46,10 +54,26 @@ export function createCtrlsGetter(cvsEl: HTMLCanvasElement) { rotate: new Rect(-hfRtSz, -hfH - sz * 2 - hfRtSz, rtSz, rtSz, rect), }; } - return { - rectCtrlsGetter, - destroy: () => { - cvsResizeOb.disconnect(); - }, + rectGetterCache.set(cvsEl, rectCtrlsGetter); + return rectCtrlsGetter(rect); +} + +// 复用 canvas 比例的获取,避免重复 observer +const cvsRatioCache = new WeakMap(); +export function getCvsRatio(cvsEl: HTMLCanvasElement): ICvsRatio { + if (cvsRatioCache.has(cvsEl)) { + return cvsRatioCache.get(cvsEl)!; + } + + const cvsRatio = { + w: cvsEl.clientWidth / cvsEl.width, + h: cvsEl.clientHeight / cvsEl.height, }; + const observer = new ResizeObserver(() => { + cvsRatio.w = cvsEl.clientWidth / cvsEl.width; + cvsRatio.h = cvsEl.clientHeight / cvsEl.height; + }); + observer.observe(cvsEl); + cvsRatioCache.set(cvsEl, cvsRatio); + return cvsRatio; } diff --git a/packages/av-cliper/CHANGELOG.md b/packages/av-cliper/CHANGELOG.md index b005a812..6149a5e9 100644 --- a/packages/av-cliper/CHANGELOG.md +++ b/packages/av-cliper/CHANGELOG.md @@ -1,5 +1,11 @@ # @webav/av-cliper +## 1.1.3-snapshot.0 + +### Patch Changes + +- @webav/internal-utils@1.1.3-snapshot.0 + ## 1.1.2 ### Patch Changes diff --git a/packages/av-cliper/package.json b/packages/av-cliper/package.json index ca6e8d7d..fb968013 100644 --- a/packages/av-cliper/package.json +++ b/packages/av-cliper/package.json @@ -1,6 +1,6 @@ { "name": "@webav/av-cliper", - "version": "1.1.2", + "version": "1.1.3-snapshot.0", "private": false, "repository": "https://github.com/WebAV-Tech/WebAV", "keywords": [ diff --git a/packages/av-cliper/src/av-utils.ts b/packages/av-cliper/src/av-utils.ts index 9eac9402..f5423ac1 100644 --- a/packages/av-cliper/src/av-utils.ts +++ b/packages/av-cliper/src/av-utils.ts @@ -129,7 +129,7 @@ export function adjustAudioDataVolume(ad: AudioData, volume: number) { sampleRate: ad.sampleRate, numberOfChannels: ad.numberOfChannels, timestamp: ad.timestamp, - format: ad.format, + format: ad.format!, numberOfFrames: ad.numberOfFrames, data, }); @@ -320,22 +320,6 @@ export function ringSliceFloat32Array( return rs; } -/** - * 函数节流 - */ -export function throttle any>( - func: F, - wait: number, -): (...rest: Parameters) => undefined | ReturnType { - let lastTime: number; - return function (this: any, ...rest) { - if (lastTime == null || performance.now() - lastTime > wait) { - lastTime = performance.now(); - return func.apply(this, rest); - } - }; -} - /** * 改变 PCM 数据的播放速率,1 表示正常播放,0.5 表示播放速率减半,2 表示播放速率加倍 */ diff --git a/packages/av-recorder/CHANGELOG.md b/packages/av-recorder/CHANGELOG.md index 43620ed2..266f512b 100644 --- a/packages/av-recorder/CHANGELOG.md +++ b/packages/av-recorder/CHANGELOG.md @@ -1,5 +1,12 @@ # @webav/av-recorder +## 1.1.3-snapshot.0 + +### Patch Changes + +- @webav/av-cliper@1.1.3-snapshot.0 +- @webav/internal-utils@1.1.3-snapshot.0 + ## 1.1.2 ### Patch Changes diff --git a/packages/av-recorder/package.json b/packages/av-recorder/package.json index 46e0aa7a..5b5abadb 100644 --- a/packages/av-recorder/package.json +++ b/packages/av-recorder/package.json @@ -1,6 +1,6 @@ { "name": "@webav/av-recorder", - "version": "1.1.2", + "version": "1.1.3-snapshot.0", "private": false, "repository": "https://github.com/WebAV-Tech/WebAV", "keywords": [ diff --git a/packages/internal-utils/CHANGELOG.md b/packages/internal-utils/CHANGELOG.md index 8f8d1162..a7bff609 100644 --- a/packages/internal-utils/CHANGELOG.md +++ b/packages/internal-utils/CHANGELOG.md @@ -1,5 +1,7 @@ # @webav/internal-utils +## 1.1.3-snapshot.0 + ## 1.1.2 ## 1.1.1 diff --git a/packages/internal-utils/package.json b/packages/internal-utils/package.json index 461d15b2..f0717b63 100644 --- a/packages/internal-utils/package.json +++ b/packages/internal-utils/package.json @@ -1,6 +1,6 @@ { "name": "@webav/internal-utils", - "version": "1.1.2", + "version": "1.1.3-snapshot.0", "private": false, "repository": "https://github.com/WebAV-Tech/WebAV", "type": "module", diff --git a/packages/internal-utils/src/index.ts b/packages/internal-utils/src/index.ts index e8ac1e63..18ba864b 100644 --- a/packages/internal-utils/src/index.ts +++ b/packages/internal-utils/src/index.ts @@ -3,3 +3,40 @@ export { workerTimer } from './worker-timer'; export { autoReadStream, file2stream } from './stream-utils'; export { recodemux } from './recodemux'; export { Log } from './log'; + +/** + * 函数节流 + */ +export function throttle any>( + func: F, + wait: number, +): (...rest: Parameters) => undefined | ReturnType { + let lastTime: number; + return function (this: any, ...rest) { + if (lastTime == null || performance.now() - lastTime > wait) { + lastTime = performance.now(); + return func.apply(this, rest); + } + }; +} + +/** + * 函数防抖 + * 在指定时间内多次调用,只执行最后一次 + */ +export function debounce any>( + func: F, + wait: number, +): (...rest: Parameters) => void { + let timer = 0; + + return function (this: any, ...args: Parameters): void { + // 清除之前的定时器 + if (timer !== 0) clearTimeout(timer); + + // 设置新的定时器 + timer = setTimeout(() => { + func.apply(this, args); + }, wait) as unknown as number; + }; +}