From 4ca3eb6c92d1d2d00b41655db4c7b2cdbd6530ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Sun, 3 May 2026 19:46:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=A2=A8=E4=B8=9D?= =?UTF-8?q?=E6=95=88=E6=9E=9C=EF=BC=8Cspreading=E9=98=B6=E6=AE=B5=E7=B2=92?= =?UTF-8?q?=E5=AD=90=E9=97=B4=E7=BB=98=E5=88=B6=E6=B8=90=E5=8F=98=E6=9B=B2?= =?UTF-8?q?=E7=BA=BF=E8=BF=9E=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/effects/ink-data-morph.tsx | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/components/effects/ink-data-morph.tsx b/src/components/effects/ink-data-morph.tsx index 12bf29c..0667d40 100644 --- a/src/components/effects/ink-data-morph.tsx +++ b/src/components/effects/ink-data-morph.tsx @@ -465,6 +465,67 @@ function drawParticle( } } +function drawInkStrings( + ctx: CanvasRenderingContext2D, + particles: Particle[], + config: ResponsiveConfig, +): void { + const spreading = particles.filter( + p => p.phase === 'spreading' && p.age >= p.delay + ); + if (spreading.length < 2) { + return; + } + + const threshold = config.connectionDistance * config.inkStringThreshold; + + ctx.save(); + ctx.lineCap = 'round'; + for (let i = 0; i < spreading.length; i++) { + const a = spreading[i]; + if (!a) { + continue; + } + for (let j = i + 1; j < spreading.length; j++) { + const b = spreading[j]; + if (!b) { + continue; + } + const dx = a.x - b.x; + const dy = a.y - b.y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < threshold && dist > 0) { + const distRatio = 1 - dist / threshold; + const alpha = distRatio * 0.15 * Math.min(a.opacity, b.opacity); + + const useAccent = ACCENT_TONES.includes(a.toneIndex) || ACCENT_TONES.includes(b.toneIndex); + const rgb = useAccent ? TONES[3].rgb : TONES[0].rgb; + + const mx = (a.x + b.x) / 2; + const my = (a.y + b.y) / 2; + const perpX = -dy / dist; + const perpY = dx / dist; + const curvature = (Math.random() - 0.5) * dist * 0.2; + const cpx = mx + perpX * curvature; + const cpy = my + perpY * curvature; + + const grad = ctx.createLinearGradient(a.x, a.y, b.x, b.y); + grad.addColorStop(0, `rgba(${rgb}, ${alpha * 0.3})`); + grad.addColorStop(0.5, `rgba(${rgb}, ${alpha})`); + grad.addColorStop(1, `rgba(${rgb}, ${alpha * 0.3})`); + + ctx.beginPath(); + ctx.moveTo(a.x, a.y); + ctx.quadraticCurveTo(cpx, cpy, b.x, b.y); + ctx.strokeStyle = grad; + ctx.lineWidth = Math.max(0.2, distRatio * 1.5); + ctx.stroke(); + } + } + } + ctx.restore(); +} + function drawConnections( ctx: CanvasRenderingContext2D, particles: Particle[], @@ -683,6 +744,7 @@ export function InkDataMorph({ } drawConnections(ctx, finalParticles, config.connectionDistance); + drawInkStrings(ctx, finalParticles, config); }, []); useEffect(() => { @@ -746,6 +808,7 @@ export function InkDataMorph({ } drawConnections(ctx, particles, config.connectionDistance); + drawInkStrings(ctx, particles, config); if (allComplete) { animationRef.current = undefined;