From 9bba35027e7b6643c6c7cf8a0ca49849e0df4677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Sun, 3 May 2026 19:51:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=AE=A3=E7=BA=B8?= =?UTF-8?q?=E7=BA=B9=E7=90=86=E5=8F=A0=E5=8A=A0=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?OffscreenCanvas=E7=94=9F=E6=88=90=E5=8F=AF=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E7=BA=B9=E7=90=86=E5=9B=BE=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/effects/ink-data-morph.tsx | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/components/effects/ink-data-morph.tsx b/src/components/effects/ink-data-morph.tsx index 0667d40..0a039ef 100644 --- a/src/components/effects/ink-data-morph.tsx +++ b/src/components/effects/ink-data-morph.tsx @@ -163,6 +163,59 @@ function getResponsiveConfig(W: number): ResponsiveConfig { }; } +function generatePaperTexture(size: number): HTMLCanvasElement { + const offscreen = document.createElement('canvas'); + offscreen.width = size; + offscreen.height = size; + const octx = offscreen.getContext('2d'); + if (!octx) { + return offscreen; + } + + octx.fillStyle = '#FAFAF5'; + octx.fillRect(0, 0, size, size); + + for (let i = 0; i < size * size * 0.15; i++) { + const x = Math.random() * size; + const y = Math.random() * size; + const gray = 200 + Math.floor(Math.random() * 40); + const alpha = 0.02 + Math.random() * 0.04; + octx.fillStyle = `rgba(${gray}, ${gray}, ${gray}, ${alpha})`; + octx.fillRect(x, y, 1, 1); + } + + octx.strokeStyle = 'rgba(180, 175, 168, 0.015)'; + octx.lineWidth = 0.5; + for (let i = 0; i < size * 0.3; i++) { + const y = Math.random() * size; + const startX = Math.random() * size * 0.3; + const endX = startX + size * (0.1 + Math.random() * 0.4); + octx.beginPath(); + octx.moveTo(startX, y); + octx.lineTo(Math.min(endX, size), y + (Math.random() - 0.5) * 2); + octx.stroke(); + } + + return offscreen; +} + +function drawPaperTexture( + ctx: CanvasRenderingContext2D, + W: number, + H: number, + texture: HTMLCanvasElement, +): void { + const pattern = ctx.createPattern(texture, 'repeat'); + if (!pattern) { + return; + } + ctx.save(); + ctx.fillStyle = pattern; + ctx.globalAlpha = 0.03; + ctx.fillRect(0, 0, W, H); + ctx.restore(); +} + function easeInOutCubic(t: number): number { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } @@ -700,6 +753,7 @@ export function InkDataMorph({ const animationRef = useRef(undefined); const startTimeRef = useRef(0); const configRef = useRef(null); + const paperTextureRef = useRef(null); const shouldReduceMotion = useReducedMotion(); const drawStaticFinal = useCallback(() => { @@ -773,6 +827,7 @@ export function InkDataMorph({ const config = getResponsiveConfig(W); configRef.current = config; particlesRef.current = initParticlesArray(W, H, config); + paperTextureRef.current = generatePaperTexture(config.paperTextureSize); if (shouldReduceMotion) { drawStaticFinal(); @@ -810,6 +865,10 @@ export function InkDataMorph({ drawConnections(ctx, particles, config.connectionDistance); drawInkStrings(ctx, particles, config); + if (paperTextureRef.current) { + drawPaperTexture(ctx, W, H, paperTextureRef.current); + } + if (allComplete) { animationRef.current = undefined; return;