import { PlaitBoard } from '@plait/core'; import { drainPoints, drawLaserPen, IOriginalPointData, setColor, setDelay, setMaxWidth, setMinWidth, setOpacity, setRoundCap, } from 'laser-pen'; export const LASER_POINTER_CLASS_NAME = 'laser-pointer'; const calculateRatio = (context: any): number => { const backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; return (window.devicePixelRatio || 1) / backingStore; }; export class LaserPointer { private mouseTrack: IOriginalPointData[] = []; private mouseMoveHandler: ((event: MouseEvent) => void) | null = null; private resizeHandler: (() => void) | null = null; private cvsDom: HTMLCanvasElement | null = null; private ctx: CanvasRenderingContext2D | null = null; private canvasPos: DOMRect | null = null; private drawing = false; private container: HTMLElement | null = null; public init(board: PlaitBoard): void { this.container = PlaitBoard.getBoardContainer(board).closest( '.drawnix' ) as HTMLElement; this.cvsDom = this.container.querySelector( `.${LASER_POINTER_CLASS_NAME}` ) as HTMLCanvasElement; this.ctx = this.cvsDom.getContext('2d') as CanvasRenderingContext2D; this.canvasPos = this.cvsDom.getBoundingClientRect(); this.mouseMoveHandler = (event: MouseEvent) => { if (!this.canvasPos) return; const relativeX = event.clientX - this.canvasPos.x; const relativeY = event.clientY - this.canvasPos.y; this.mouseTrack.push({ x: relativeX, y: relativeY, time: Date.now(), }); this.ctx && this.startDraw(); }; this.resizeHandler = () => this.setCanvasSize(); this.container.addEventListener('pointermove', this.mouseMoveHandler); window.addEventListener('resize', this.resizeHandler); this.setCanvasSize(); } public destroy(): void { if (this.mouseMoveHandler && this.container) { this.container.removeEventListener('pointermove', this.mouseMoveHandler); this.mouseMoveHandler = null; } if (this.resizeHandler) { window.removeEventListener('resize', this.resizeHandler); this.resizeHandler = null; } if (this.ctx && this.cvsDom) { this.ctx.clearRect(0, 0, this.cvsDom.width, this.cvsDom.height); } this.cvsDom = null; this.ctx = null; this.canvasPos = null; this.drawing = false; } private startDraw(): void { if (!this.drawing) { this.drawing = true; this.draw(); } } private draw(): void { if (!this.ctx || !this.cvsDom) return; this.ctx.clearRect(0, 0, this.cvsDom.width, this.cvsDom.height); let needDrawInNextFrame = false; this.mouseTrack = drainPoints(this.mouseTrack); if (this.mouseTrack.length >= 3) { setColor(211, 211, 211); setDelay(180); setRoundCap(true); setMaxWidth(10); setMinWidth(0); setOpacity(0.6); drawLaserPen(this.ctx, this.mouseTrack); needDrawInNextFrame = true; } else { const centerPoint = this.mouseTrack[this.mouseTrack.length - 1]; if (!centerPoint) return; this.ctx.save(); this.ctx.beginPath(); this.ctx.fillStyle = `rgba(211, 211, 211)`; this.ctx.arc(centerPoint.x, centerPoint.y, 5, 0, Math.PI * 2, false); this.ctx.closePath(); this.ctx.fill(); this.ctx.restore(); } if (needDrawInNextFrame) { requestAnimationFrame(() => this.draw()); } else { this.drawing = false; } } private setCanvasSize(): void { if (!this.cvsDom || !this.ctx) return; const rect = this.cvsDom.getBoundingClientRect(); const ratio = calculateRatio(this.ctx); this.cvsDom.setAttribute('width', `${rect.width * ratio}px`); this.cvsDom.setAttribute('height', `${rect.height * ratio}px`); this.ctx.scale(ratio, ratio); this.canvasPos = this.cvsDom.getBoundingClientRect(); } }