From 9b1939dd028de7fddf8825b55d000a01da16c780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Sun, 3 May 2026 19:40:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B04=E5=B1=82=E6=99=95?= =?UTF-8?q?=E6=9F=93=E7=BB=98=E5=88=B6=E7=B3=BB=E7=BB=9F=EF=BC=8C=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E5=8E=9F=E6=9C=89=E6=B8=90=E5=8F=98=E7=BB=98=E5=88=B6?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/effects/ink-data-morph.tsx | 136 ++++++++++++++-------- 1 file changed, 90 insertions(+), 46 deletions(-) diff --git a/src/components/effects/ink-data-morph.tsx b/src/components/effects/ink-data-morph.tsx index 2679c80..016fec3 100644 --- a/src/components/effects/ink-data-morph.tsx +++ b/src/components/effects/ink-data-morph.tsx @@ -289,6 +289,94 @@ function drawInkDot( ctx.restore(); } +function drawInkBlob( + ctx: CanvasRenderingContext2D, + cx: number, + cy: number, + baseR: number, + seed1: number, + seed2: number, + wobble: number, + detail: number, + rgb: string, + opacity: number, +): void { + ctx.beginPath(); + for (let i = 0; i <= detail; i++) { + const angle = (i / detail) * Math.PI * 2; + const noise = Math.sin(angle * 3 + seed1) * 0.3 + Math.cos(angle * 5 + seed2) * 0.2; + const r = Math.max(0.5, baseR * (1 + noise * wobble)); + const x = cx + Math.cos(angle) * r; + const y = cy + Math.sin(angle) * r; + if (i === 0) { + ctx.moveTo(x, y); + } else { + ctx.lineTo(x, y); + } + } + ctx.closePath(); + ctx.fillStyle = `rgba(${rgb}, ${opacity})`; + ctx.fill(); +} + +function drawInkLayers( + ctx: CanvasRenderingContext2D, + p: Particle, + config: ResponsiveConfig, +): void { + const tone = TONES[p.toneIndex]; + if (!tone) { + return; + } + + const isAccent = ACCENT_TONES.includes(p.toneIndex); + const deepRgb = isAccent ? tone.rgb : (p.toneIndex <= 1 ? tone.rgb : TONES[0].rgb); + const lightRgb = isAccent ? TONES[1].rgb : (p.toneIndex >= 2 ? tone.rgb : TONES[2].rgb); + + const r = Math.max(0.5, p.radius) * config.inkRadiusScale; + const op = p.opacity; + const detail = config.wobbleDetail; + + const layers: Array<{ radiusMul: number; opacityMul: number; rgb: string; wobble: number; useRect: boolean }> = [ + { radiusMul: 5, opacityMul: 0.04, rgb: lightRgb, wobble: 0.25, useRect: true }, + { radiusMul: 3, opacityMul: 0.12, rgb: lightRgb, wobble: 0.2, useRect: true }, + { radiusMul: 1.5, opacityMul: 0.4, rgb: deepRgb, wobble: 0.12, useRect: false }, + { radiusMul: 1, opacityMul: 0.9, rgb: deepRgb, wobble: 0.06, useRect: false }, + ]; + + const layerCount = p.inkLayerCount; + const startIdx = layers.length - layerCount; + + for (let i = startIdx; i < layers.length; i++) { + const layer = layers[i]; + if (!layer) { + continue; + } + const layerR = r * layer.radiusMul; + const layerOp = op * layer.opacityMul; + + if (layer.useRect) { + const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, layerR); + grad.addColorStop(0, `rgba(${layer.rgb}, ${layerOp})`); + grad.addColorStop(0.5, `rgba(${layer.rgb}, ${layerOp * 0.3})`); + grad.addColorStop(1, 'transparent'); + ctx.fillStyle = grad; + ctx.fillRect(p.x - layerR, p.y - layerR, layerR * 2, layerR * 2); + } else { + const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, layerR); + grad.addColorStop(0, `rgba(${layer.rgb}, ${layerOp})`); + grad.addColorStop(0.6, `rgba(${layer.rgb}, ${layerOp * 0.5})`); + grad.addColorStop(1, 'transparent'); + ctx.fillStyle = grad; + ctx.beginPath(); + ctx.arc(p.x, p.y, layerR, 0, Math.PI * 2); + ctx.fill(); + + drawInkBlob(ctx, p.x, p.y, layerR * 0.7, p.seed1, p.seed2, layer.wobble, detail, layer.rgb, layerOp * 0.6); + } + } +} + function drawParticle( ctx: CanvasRenderingContext2D, p: Particle, @@ -309,53 +397,9 @@ function drawParticle( if (p.phase === 'spreading') { drawTrail(ctx, p, rgb); - - if (config.gradientLayers >= 3) { - const grad3 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 5 * gs); - grad3.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.06})`); - grad3.addColorStop(0.5, `rgba(${rgb}, ${p.opacity * 0.02})`); - grad3.addColorStop(1, 'transparent'); - ctx.fillStyle = grad3; - ctx.fillRect(p.x - r * 5 * gs, p.y - r * 5 * gs, r * 10 * gs, r * 10 * gs); - } - - const grad2R = r * 2.8 * gs; - const grad2 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, grad2R); - grad2.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.2})`); - grad2.addColorStop(0.6, `rgba(${rgb}, ${p.opacity * 0.06})`); - grad2.addColorStop(1, 'transparent'); - ctx.fillStyle = grad2; - ctx.fillRect(p.x - grad2R, p.y - grad2R, grad2R * 2, grad2R * 2); - - const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r); - grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.9})`); - grad1.addColorStop(0.7, `rgba(${rgb}, ${p.opacity * 0.4})`); - grad1.addColorStop(1, 'transparent'); - ctx.fillStyle = grad1; - ctx.beginPath(); - ctx.arc(p.x, p.y, r, 0, Math.PI * 2); - ctx.fill(); - - drawInkDot(ctx, p.x, p.y, r * 0.6, p.rotation, p.scaleX, p.scaleY, rgb, p.opacity * 0.7); + drawInkLayers(ctx, p, config); } else if (p.phase === 'settling') { - const grad2R = r * 2.2 * gs; - const grad2 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, grad2R); - grad2.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.15})`); - grad2.addColorStop(0.5, `rgba(${rgb}, ${p.opacity * 0.05})`); - grad2.addColorStop(1, 'transparent'); - ctx.fillStyle = grad2; - ctx.fillRect(p.x - grad2R, p.y - grad2R, grad2R * 2, grad2R * 2); - - const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r); - grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.85})`); - grad1.addColorStop(0.6, `rgba(${rgb}, ${p.opacity * 0.3})`); - grad1.addColorStop(1, 'transparent'); - ctx.fillStyle = grad1; - ctx.beginPath(); - ctx.arc(p.x, p.y, r, 0, Math.PI * 2); - ctx.fill(); - - drawInkDot(ctx, p.x, p.y, r * 0.5, p.rotation, p.scaleX, p.scaleY, rgb, p.opacity * 0.5); + drawInkLayers(ctx, p, config); } else if (p.phase === 'fading') { const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 1.5); grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.6})`);