UNPKG

@tsparticles/effect-trail

Version:
110 lines (109 loc) 5.64 kB
import { OutMode, OutModeDirection, defaultAlpha, double, getRangeValue, half, originPoint, } from "@tsparticles/engine"; const minTrailLength = 3, trailLengthOffset = 1, minWidth = -1, firstIndex = 0, defaultLength = 10, loopTrailLengthOffset = 2, loopTrailLengthMinIndex = 0, defaultWidth = 0, minBound = 0; const defaultTransform = { a: 1, b: 0, c: 0, d: 1, }; export class TrailDrawer { #container; constructor(container) { this.#container = container; } drawAfter(data) { const { context, drawPosition, drawRadius, drawScale, particle, transformData } = data, container = this.#container, diameter = drawRadius * double, pxRatio = container.retina.pixelRatio, trail = particle.trail; if (!trail || !particle.trailLength) { return; } const currentPos = drawPosition, pathLength = particle.trailLength * drawScale + drawRadius; trail.push({ break: particle.justWarped, color: context.fillStyle || context.strokeStyle, position: { x: currentPos.x, y: currentPos.y, }, transformData, }); if (trail.length < minTrailLength) { return; } const pathLengthFloor = Math.floor(pathLength); if (trail.length > pathLengthFloor) { trail.splice(firstIndex, trail.length - pathLengthFloor); } const trailLength = Math.min(trail.length, pathLength); context.save(); context.lineCap = "butt"; context.lineJoin = "round"; for (let i = trailLength - loopTrailLengthOffset; i > loopTrailLengthMinIndex; i--) { const previousStep = trail[i + trailLengthOffset], step = trail[i], nextStep = trail[i - trailLengthOffset]; if (!previousStep || !step || !nextStep) { continue; } const previousPosition = previousStep.position, position = step.position, nextPosition = nextStep.position, stepTransformData = particle.trailTransform ? (step.transformData ?? defaultTransform) : defaultTransform; if (step.break || previousStep.break || nextStep.break) { continue; } context.setTransform(stepTransformData.a, stepTransformData.b, stepTransformData.c, stepTransformData.d, position.x, position.y); const startX = (previousPosition.x + position.x) * half - position.x, startY = (previousPosition.y + position.y) * half - position.y, endX = (position.x + nextPosition.x) * half - position.x, endY = (position.y + nextPosition.y) * half - position.y; context.beginPath(); context.moveTo(startX, startY); context.quadraticCurveTo(originPoint.x, originPoint.y, endX, endY); const width = Math.max((i / trailLength) * diameter, pxRatio, (particle.trailMinWidth ?? minWidth) * drawScale), oldAlpha = context.globalAlpha; context.globalAlpha = particle.trailFade ? i / trailLength : defaultAlpha; context.lineWidth = particle.trailMaxWidth ? Math.min(width, particle.trailMaxWidth * drawScale) : width; context.strokeStyle = step.color; context.stroke(); context.globalAlpha = oldAlpha; } context.restore(); } isInsideCanvas(data) { const { canvasSize, direction, outMode, particle, radius } = data, position = particle.position, trail = particle.trail; let minX = position.x - radius, maxX = position.x + radius, minY = position.y - radius, maxY = position.y + radius; if (trail?.length) { const widthPadding = Math.max(radius, particle.trailMaxWidth ?? defaultWidth, particle.trailMinWidth ?? defaultWidth); for (const step of trail) { minX = Math.min(minX, step.position.x - widthPadding); maxX = Math.max(maxX, step.position.x + widthPadding); minY = Math.min(minY, step.position.y - widthPadding); maxY = Math.max(maxY, step.position.y + widthPadding); } } const strictBounds = outMode === OutMode.destroy; if (direction === OutModeDirection.bottom) { return minY <= canvasSize.height; } if (direction === OutModeDirection.left) { return maxX >= minBound; } if (direction === OutModeDirection.right) { return minX <= canvasSize.width; } if (direction === OutModeDirection.top) { return maxY >= minBound; } if (!strictBounds) { return (position.x + radius >= minBound && position.y + radius >= minBound && position.x - radius <= canvasSize.width && position.y - radius <= canvasSize.height); } return maxX >= minBound && maxY >= minBound && minX <= canvasSize.width && minY <= canvasSize.height; } particleInit(container, particle) { particle.trail = []; const effectData = particle.effectData; particle.trailFade = effectData?.fade ?? true; particle.trailLength = getRangeValue(effectData?.length ?? defaultLength) * container.retina.pixelRatio; particle.trailMaxWidth = effectData?.maxWidth ? getRangeValue(effectData.maxWidth) * container.retina.pixelRatio : undefined; particle.trailMinWidth = effectData?.minWidth ? getRangeValue(effectData.minWidth) * container.retina.pixelRatio : undefined; particle.trailTransform = effectData?.transform ?? false; } }