Skip to content

Commit 535af0e

Browse files
authored
feat(eraser): drawing erasing trail animation effect (#295)
* feat(eraser): drawing eraseing effect and extract laser-pointer class to drawing laser effect This pull request will implement eraser trail animation effect mentioned in #247 * chore: fix lint errors * feat: improve laser pointer
1 parent d9b6371 commit 535af0e

File tree

6 files changed

+172
-0
lines changed

6 files changed

+172
-0
lines changed

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"scripts": {
66
"start": "nx serve web --host=0.0.0.0",
77
"build": "nx run-many -t=build",
8+
"lint": "nx run-many --target=lint --all --fix",
89
"build:web": "nx build web",
910
"test": "nx run-many -t=test",
1011
"release": "node scripts/release-version.js",
@@ -24,6 +25,7 @@
2425
"ahooks": "^3.8.0",
2526
"browser-fs-access": "^0.35.0",
2627
"classnames": "^2.5.1",
28+
"laser-pen": "^1.0.1",
2729
"localforage": "^1.10.0",
2830
"mobile-detect": "^1.4.5",
2931
"open-color": "^1.9.1",

packages/drawnix/src/drawnix.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { buildTextLinkPlugin } from './plugins/with-text-link';
3939
import { LinkPopup } from './components/popup/link-popup/link-popup';
4040
import { I18nProvider } from './i18n';
4141
import { Tutorial } from './components/tutorial';
42+
import { LASER_POINTER_CLASS_NAME } from './utils/laser-pointer';
4243

4344
export type DrawnixProps = {
4445
value: PlaitElement[];
@@ -156,6 +157,7 @@ export const Drawnix: React.FC<DrawnixProps> = ({
156157
<TTDDialog container={containerRef.current}></TTDDialog>
157158
<CleanConfirm container={containerRef.current}></CleanConfirm>
158159
</Wrapper>
160+
<canvas className={`${LASER_POINTER_CLASS_NAME} mouse-course-hidden`}></canvas>
159161
</div>
160162
</DrawnixContext.Provider>
161163
</I18nProvider>

packages/drawnix/src/plugins/freehand/with-freehand-erase.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import { isDrawingMode } from '@plait/common';
1010
import { isHitFreehand } from './utils';
1111
import { Freehand, FreehandShape } from './type';
1212
import { CoreTransforms } from '@plait/core';
13+
import { LaserPointer } from '../../utils/laser-pointer';
1314

1415
export const withFreehandErase = (board: PlaitBoard) => {
1516
const { pointerDown, pointerMove, pointerUp, globalPointerUp } = board;
1617

18+
const laserPointer = new LaserPointer();
19+
1720
let isErasing = false;
1821
const elementsToDelete = new Set<string>();
1922

@@ -49,6 +52,7 @@ export const withFreehandErase = (board: PlaitBoard) => {
4952
deleteMarkedElements();
5053
isErasing = false;
5154
elementsToDelete.clear();
55+
laserPointer.destroy();
5256
}
5357
};
5458

@@ -60,6 +64,7 @@ export const withFreehandErase = (board: PlaitBoard) => {
6064
elementsToDelete.clear();
6165
const currentPoint: Point = [event.x, event.y];
6266
checkAndMarkFreehandElementsForDeletion(currentPoint);
67+
laserPointer.init(board);
6368
return;
6469
}
6570

packages/drawnix/src/styles/index.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@
134134
cursor: zoom-in;
135135
}
136136
}
137+
138+
.laser-pointer {
139+
background: transparent;
140+
position: fixed;
141+
left: 0;
142+
top: 0;
143+
z-index: 2022;
144+
width: 100vw;
145+
height: 100vh;
146+
&.mouse-course-hidden {
147+
pointer-events: none;
148+
}
149+
}
137150
}
138151

139152
.plait-board-container {
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { PlaitBoard } from '@plait/core';
2+
import {
3+
drainPoints,
4+
drawLaserPen,
5+
IOriginalPointData,
6+
setColor,
7+
setDelay,
8+
setMaxWidth,
9+
setMinWidth,
10+
setOpacity,
11+
setRoundCap,
12+
} from 'laser-pen';
13+
14+
export const LASER_POINTER_CLASS_NAME = 'laser-pointer';
15+
16+
const calculateRatio = (context: any): number => {
17+
const backingStore =
18+
context.backingStorePixelRatio ||
19+
context.webkitBackingStorePixelRatio ||
20+
context.mozBackingStorePixelRatio ||
21+
context.msBackingStorePixelRatio ||
22+
context.oBackingStorePixelRatio ||
23+
context.backingStorePixelRatio ||
24+
1;
25+
return (window.devicePixelRatio || 1) / backingStore;
26+
};
27+
28+
export class LaserPointer {
29+
private mouseTrack: IOriginalPointData[] = [];
30+
private mouseMoveHandler: ((event: MouseEvent) => void) | null = null;
31+
private resizeHandler: (() => void) | null = null;
32+
private cvsDom: HTMLCanvasElement | null = null;
33+
private ctx: CanvasRenderingContext2D | null = null;
34+
private canvasPos: DOMRect | null = null;
35+
private drawing = false;
36+
private container: HTMLElement | null = null;
37+
38+
public init(board: PlaitBoard): void {
39+
this.container = PlaitBoard.getBoardContainer(board).closest(
40+
'.drawnix'
41+
) as HTMLElement;
42+
this.cvsDom = this.container.querySelector(
43+
`.${LASER_POINTER_CLASS_NAME}`
44+
) as HTMLCanvasElement;
45+
this.ctx = this.cvsDom.getContext('2d') as CanvasRenderingContext2D;
46+
this.canvasPos = this.cvsDom.getBoundingClientRect();
47+
48+
this.mouseMoveHandler = (event: MouseEvent) => {
49+
if (!this.canvasPos) return;
50+
const relativeX = event.clientX - this.canvasPos.x;
51+
const relativeY = event.clientY - this.canvasPos.y;
52+
this.mouseTrack.push({
53+
x: relativeX,
54+
y: relativeY,
55+
time: Date.now(),
56+
});
57+
this.ctx && this.startDraw();
58+
};
59+
60+
this.resizeHandler = () => this.setCanvasSize();
61+
this.container.addEventListener('pointermove', this.mouseMoveHandler);
62+
window.addEventListener('resize', this.resizeHandler);
63+
64+
this.setCanvasSize();
65+
}
66+
67+
public destroy(): void {
68+
if (this.mouseMoveHandler && this.container) {
69+
this.container.removeEventListener('pointermove', this.mouseMoveHandler);
70+
this.mouseMoveHandler = null;
71+
}
72+
73+
if (this.resizeHandler) {
74+
window.removeEventListener('resize', this.resizeHandler);
75+
this.resizeHandler = null;
76+
}
77+
78+
if (this.ctx && this.cvsDom) {
79+
this.ctx.clearRect(0, 0, this.cvsDom.width, this.cvsDom.height);
80+
}
81+
82+
this.cvsDom = null;
83+
this.ctx = null;
84+
this.canvasPos = null;
85+
this.drawing = false;
86+
}
87+
88+
private startDraw(): void {
89+
if (!this.drawing) {
90+
this.drawing = true;
91+
this.draw();
92+
}
93+
}
94+
95+
private draw(): void {
96+
if (!this.ctx || !this.cvsDom) return;
97+
98+
this.ctx.clearRect(0, 0, this.cvsDom.width, this.cvsDom.height);
99+
let needDrawInNextFrame = false;
100+
101+
this.mouseTrack = drainPoints(this.mouseTrack);
102+
if (this.mouseTrack.length >= 3) {
103+
setColor(211, 211, 211);
104+
setDelay(180);
105+
setRoundCap(true);
106+
setMaxWidth(10);
107+
setMinWidth(0);
108+
setOpacity(0.6);
109+
drawLaserPen(this.ctx, this.mouseTrack);
110+
needDrawInNextFrame = true;
111+
} else {
112+
const centerPoint = this.mouseTrack[this.mouseTrack.length - 1];
113+
if (!centerPoint) return;
114+
115+
this.ctx.save();
116+
this.ctx.beginPath();
117+
this.ctx.fillStyle = `rgba(211, 211, 211)`;
118+
this.ctx.arc(centerPoint.x, centerPoint.y, 5, 0, Math.PI * 2, false);
119+
this.ctx.closePath();
120+
this.ctx.fill();
121+
this.ctx.restore();
122+
}
123+
124+
if (needDrawInNextFrame) {
125+
requestAnimationFrame(() => this.draw());
126+
} else {
127+
this.drawing = false;
128+
}
129+
}
130+
131+
private setCanvasSize(): void {
132+
if (!this.cvsDom || !this.ctx) return;
133+
134+
const rect = this.cvsDom.getBoundingClientRect();
135+
const ratio = calculateRatio(this.ctx);
136+
137+
this.cvsDom.setAttribute('width', `${rect.width * ratio}px`);
138+
this.cvsDom.setAttribute('height', `${rect.height * ratio}px`);
139+
this.ctx.scale(ratio, ratio);
140+
141+
this.canvasPos = this.cvsDom.getBoundingClientRect();
142+
}
143+
}

0 commit comments

Comments
 (0)