Skip to content

fix: sprites beyond canvas boundaries cannot interact #410

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -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"]
}
5 changes: 5 additions & 0 deletions .changeset/thirty-mangos-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@webav/av-canvas': patch
---

fix: sprites beyond canvas boundaries cannot interact
15 changes: 6 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<!--
ChatGPT:
## Sponsors

你是一位精通中英文的资深翻译,长时间从事计算机领域的技术文章翻译工作。
We would like to thank our GitHub sponsors who support the development of WebAV:

我正在为一个音视频开源项目撰写 README 中文文档,需要你帮忙将中文翻译为英文。
<div>
<a href="https://github.com/425776024"><img src="https://github.com/425776024.png" width="50" height="50" alt="425776024" /></a>
</div>

翻译过程有以下要求:
- 翻译结果需符合英语母语者的习惯,符合技术文章规范,可以进行适当润色
- 翻译的内容为 Markdown 文本,翻译结果应该保持 Markdown 格式
- 不需要翻译 html、js 代码,但需要翻译代码中中文注释
- 不需要翻译 URL 链接 -->
Your support helps us continue to improve and maintain this project!
10 changes: 10 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,13 @@ recorder.start(); // => ReadableStream
---

加微信 `liujun_fenghen` 备注 WebAV,邀请进入 WebAV 音视频技术交流 微信群

## 赞助者

我们要感谢以下 GitHub 赞助者对 WebAV 开发的支持:

<div>
<a href="https://github.com/425776024"><img src="https://github.com/425776024.png" width="50" height="50" alt="425776024" /></a>
</div>

您的支持帮助我们持续改进和维护这个项目!
8 changes: 8 additions & 0 deletions packages/av-canvas/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/av-canvas/package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
65 changes: 35 additions & 30 deletions packages/av-canvas/src/__tests__/av-canvas.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof createCtrlsGetter>['rectCtrlsGetter'];
let ctrlGetterDestroy: () => void;
beforeEach(() => {
container = createEl('div') as HTMLDivElement;
container.style.cssText = `
Expand All @@ -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();
});
Expand All @@ -48,40 +43,50 @@ 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;
vs.rect.w = 100;
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 () => {
Expand Down
21 changes: 6 additions & 15 deletions packages/av-canvas/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof createCtrlsGetter>['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)]),
Expand All @@ -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',
Expand All @@ -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',
Expand Down
27 changes: 6 additions & 21 deletions packages/av-canvas/src/av-canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -107,31 +103,20 @@ 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);

createEmptyOscillatorNode(this.#audioCtx).connect(this.#captureAudioDest);

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;
// 默认居中
Expand Down
Loading
Loading