import { isFiniteNumber } from '@/internal/isFiniteNumber'; import { lerp } from '@/utils'; import { LERP_APPROXIMATION } from '../constants'; import { svgQuadraticCurvePath } from './svgQuadraticCurvePath'; import { ICursorPathPoint, ICursorPathVec2 } from './types'; export class CursorPath { /** Cursor SVG Path Points */ private _points: ICursorPathPoint[] = []; /** Cursor SVG Path */ private _path: SVGPathElement; /** Cursor SVG Path Line */ private _line = { current: 0, target: 0 }; /** Cursor SVG Path */ get path() { return this._path; } constructor(private _isEnabled: boolean) { this._path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path', )!; const path = this._path; path.setAttribute('stroke-linecap', 'round'); path.setAttribute('stroke-linejoin', 'round'); path.setAttribute('fill', 'transparent'); path.setAttribute('stroke', '#f00'); } /** Update SVG Path */ public addPoint(coords: ICursorPathVec2, isInstant = false) { if (!this._isEnabled) { return; } const points = this._points; const path = this._path; const line = this._line; // Add point const newPoint = { x: coords.x, y: coords.y, length: 0 }; points.push(newPoint); // Update path path.setAttribute('d', svgQuadraticCurvePath(points)); // Update total length const totalLength = path.getTotalLength(); newPoint.length = totalLength; line.target = totalLength; // Instant update of line if (isInstant) { line.current = line.target; } } /** Minimize SVG Path */ public minimize() { if (!this._isEnabled) { return; } const points = this._points; const line = this._line; if (points.length < 3) { return; } let accumulated = 0; let removeCount = 0; for (let i = 1; i < points.length; i += 1) { const dx = points[i].x - points[i - 1].x; const dy = points[i].y - points[i - 1].y; const segLength = Math.hypot(dx, dy); if (accumulated + segLength < line.current) { accumulated += segLength; removeCount += 1; } else { break; } } if (isFiniteNumber(removeCount) && removeCount > 0) { let removedLength = 0; for (let i = 1; i <= removeCount; i += 1) { const dx = points[i].x - points[i - 1].x; const dy = points[i].y - points[i - 1].y; removedLength += Math.hypot(dx, dy); } points.splice(0, removeCount); // Fix line after points removed line.current = Math.max(0, line.current - removedLength); line.target = Math.max(0, line.target - removedLength); // Update path this._path.setAttribute('d', svgQuadraticCurvePath(points)); } } /** Check if the path is interpolated */ public get isInterpolated() { return this._line.current === this._line.target; } /** Interpolate line */ public lerp(factor: number) { const line = this._line; line.current = lerp(line.current, line.target, factor, LERP_APPROXIMATION); } /** Get current coordinate */ get coord() { return this._path.getPointAtLength(this._line.current); } }