// ===== demos/typography/type-entrance-moves/LetterDropPhysics.tsx ===== // 字符坠落堆积(letter-drop-physics)——FallingLetterAnimation。 // "GRAVITY" 7 字符各自绝对定位,第 i 字符从帧 10+i*5 起下落: // ① 重力加速 y = D*(t/24)^2 掉 720px 到基线(地板线可见); // ② 落地后 2 次衰减弹跳(高度 30% / 9%,抛物线 4u(1-u) 拼段), // 落地瞬间 rotate 到 seed hash ±6° 小歪角并保持; // ③ 帧 110 一拍:6f ease-out 全体齐整回正(rotate→0、偏移→0、scale 1.06→1), // 帧 116–150 真静止(≥25f)收尾。 import React from 'react'; import { useCurrentFrame, Easing } from 'remotion'; import { G } from '../../_fixtures/Fixtures'; // 确定性伪随机 const h = (n: number) => { const s = Math.sin(n * 127.3) * 43758.5453; return s - Math.floor(s); }; const WORD = 'GRAVITY'; const SLOT_W = 150; // 每字符槽宽 const FONT = 140; const WORD_W = WORD.length * SLOT_W; // 1050 const LEFT = (1920 - WORD_W) / 2; // 435 const REST_TOP = 452; // 字符落定后的 top const FLOOR_Y = REST_TOP + 152; // 地板线(视觉基线) const DROP = 720; // 下落距离 const T_FALL = 24; // 落到基线用时 const T_B1 = 16; // 第一次弹跳时长(高 30%) const T_B2 = 8; // 第二次弹跳时长(高 9%) const SNAP = 110; // 齐整回正的一拍 const SNAP_DUR = 6; const easeOutCubic = Easing.out(Easing.cubic); const clamp01 = (x: number) => Math.min(1, Math.max(0, x)); // 单字符纵向位移(相对基线,负 = 在上方) const dropY = (t: number): number => { if (t <= 0) return -DROP; if (t < T_FALL) return -DROP + DROP * (t / T_FALL) ** 2; // 重力加速 if (t < T_FALL + T_B1) { const u = (t - T_FALL) / T_B1; return -DROP * 0.3 * 4 * u * (1 - u); // 弹跳 1:30% } if (t < T_FALL + T_B1 + T_B2) { const u = (t - T_FALL - T_B1) / T_B2; return -DROP * 0.09 * 4 * u * (1 - u); // 弹跳 2:9% } return 0; }; export const LetterDropPhysics: React.FC = () => { const frame = useCurrentFrame(); // 帧 110 起的齐整回正进度 const snap = easeOutCubic(clamp01((frame - SNAP) / SNAP_DUR)); return (
{/* 地板线:字符落点的视觉基线 */}
{WORD.split('').map((ch, i) => { const start = 10 + i * 5; const t = frame - start; const y = dropY(t); // 落地瞬间歪到 seed 小角度(±6°),落地前为 0 const tiltTarget = (h(i + 1) - 0.5) * 12; const landP = clamp01((t - T_FALL) / 6); const restJitter = (h(i + 11) - 0.5) * 10; // 落定后的 ±5px 竖向错位 // snap 一拍:歪角与错位齐整归零,scale 1.06→1 脉冲 const rot = tiltTarget * landP * (1 - snap); const jitter = restJitter * landP * (1 - snap); const scale = frame < SNAP ? 1 : 1 + 0.06 * (1 - snap); return (
{ch}
); })}
); }; // ===== demos/typography/type-entrance-moves/ScrambleDecode.tsx ===== // 乱码解码字(scramble-decode)——终端黑客感文字入场。 // 标题 "DECODE SPEED" 每字符先高速跳随机字母/数字(每 2f 换一个,seed hash 取字符), // 从左到右逐个锁定:第 i 个字符在帧 20+i*6 锁定为真字符,锁定瞬间该字符反色闪 // (G.ink 色块白字 2f)随即恢复。跳动期字符 G.mid,锁定后 G.ink。 // 关键帧:0–20f 全员乱跳 → 20–86f 从左到右逐个锁定 → 87–130f 完全静止收尾。 import React from 'react'; import { useCurrentFrame } from 'remotion'; import { G } from '../../_fixtures/Fixtures'; const TEXT = 'DECODE SPEED'; const CHARSET = 'ABCDEF0123456789#$%&'; const LOCK_START = 20; // 第 0 个字符锁定帧 const LOCK_STEP = 6; // 相邻字符锁定间隔 const FLASH_LEN = 2; // 锁定反色闪持续帧数 // 帧确定伪随机 const h = (n: number) => { const s = Math.sin(n * 127.3) * 43758.5453; return s - Math.floor(s); }; export const ScrambleDecode: React.FC = () => { const frame = useCurrentFrame(); const chars = TEXT.split(''); const lockedCount = chars.filter((c, i) => c === ' ' || frame >= LOCK_START + i * LOCK_STEP).length; const allLocked = lockedCount === chars.length; return (
{chars.map((ch, i) => { if (ch === ' ') { return ; } const lockFrame = LOCK_START + i * LOCK_STEP; const locked = frame >= lockFrame; const flashing = locked && frame < lockFrame + FLASH_LEN; // 跳动期:每 2 帧换一个伪随机字符 const tick = Math.floor(frame / 2); const scrambleChar = CHARSET[Math.floor(h(i * 101 + tick * 7 + 13) * CHARSET.length)]; const shown = locked ? ch : scrambleChar; return ( {shown} ); })}
{/* 底部进度提示条:已锁定字符数比例,帮助读出"从左到右扫过"的推进感 */}
); };