// ===== demos/outro/grain-dissolve/GrainDissolve.tsx ===== // grain-dissolve — Grain Dissolve → Condense 文字砂化凝聚(motion-lab 定稿转原生 Remotion) // 干净的整行字 "{ ACME. Now Live }" 先爆裂成沸腾颗粒噪点(轮廓隐约可辨、白色辉光), // 同时出现带 45° 斜纹填充和像素方块角柄的选区框;噪点沸腾约半程后选区框消失, // 噪点云急速凝聚成更大号的颗粒短字标(占位词 "ACME"),位移量衰减归零、辉光冲高回落, // 凝固为清晰发光短字标。四角 HUD 括角/圆点与左右中线短划全程常驻。 // 滤镜链:feTurbulence seed 逐帧 + displacement scale 双向动画,终字同走滤镜再解除。 // 设计坐标 480×270(DesignStage 等比放大),SVG viewBox 640×360 铺满全幅。 import React, { useId } from 'react'; import { E, DesignStage, seg, useT } from '../../_fixtures/Motion'; export const GRAIN_DISSOLVE_DURATION = 60; // 2000ms @30fps // 选区框几何(viewBox 坐标) const BX = 128; const BY = 148; const BW = 384; const BH = 62; // 45° 斜纹:x 从 bx-bh 起每 34 一根,右下→左上 const HATCH_XS: number[] = []; for (let x = BX - BH; x < BX + BW; x += 34) HATCH_XS.push(x); // 四角像素棋盘手柄(两块 5×5 错位方块) const Handle: React.FC<{ x: number; y: number }> = ({ x, y }) => ( ); // HUD 括角 + 圆点(sx/sy 控制朝向) const Corner: React.FC<{ x: number; y: number; sx: number; sy: number }> = ({ x, y, sx, sy }) => ( <> ); export const GrainDissolve: React.FC = () => { const t = useT(); // 滤镜/clipPath ID 按实例生成,同一 Composition 放多个实例时互不串引 // (useId 的 «:» 在 CSS url() 里非法,清洗成纯字母数字) const uid = useId().replace(/[^a-zA-Z0-9]/g, ''); const fid = `gd-${uid}`; const cid = `gd-${uid}-clip`; const burst = seg(t, 0.13, 0.28, E.outCubic); // 干净字 → 砂化 const cond = seg(t, 0.60, 0.71, E.inOutCubic); // 整行噪点云 → 短字标噪点云 const lock = seg(t, 0.68, 0.90, E.outCubic); // 位移衰减凝固 const settle = seg(t, 0.88, 1, E.outCubic); // 辉光回落 // 白色辉光:砂化期轻微,凝聚时冲高,凝固后回落到柔光 const glow = burst * 0.3 + cond * 0.7 - settle * 0.45; return ( {/* 四角 HUD 括角 + 圆点 + 左右中线短划(全程常驻) */} {/* 选区框:砂化时浮现,凝聚前撤掉 */} {HATCH_XS.map((x) => ( ))} {/* 文字组:整行字与终字标同走滤镜链 + 白色辉光 */} {'{ ACME. Now Live }'} ACME ); };