// ===== demos/effects/slam-entrance-moves/ImpactBurstKit.tsx ===== // 落点冲击套件(impact-burst-kit)——shockwave-ring + particle-burst 组合变异。 // 主卡砸落的落点帧同时触发:冲击波环扩散 + 14 粒子放射迸发 + 震屏, // 且冲击波前沿扫到左右邻卡的那一帧(按半径-距离算准=落点后 3f)邻卡被 // 向外推开再 spring 弹回——"波及邻居"即两条词汇焊接成立的证据。 // 关键帧:0–14 两侧卡驻场、主卡 scale1.8/y-120 悬停 → 14–20 主卡 6f 加速砸落 → // 20 落点帧:环 80→900px(14f out-cubic, op .75→0) + 14 粒子飞散 160–340px(22f) // + 4f 震屏 6px 衰减 + 主卡 6f 压扁回弹 → 23 环前沿过邻卡(中心距 460px): // 邻卡外推 30px + rotate ±3° 阻尼振荡弹回(40f 内钳到 0) → 63–140 全静止(77f)。 import React from 'react'; import { useCurrentFrame, interpolate, Easing } from 'remotion'; import { G, Card, TitleBlock } from '../../_fixtures/Fixtures'; // 伪随机(帧确定) const h = (n: number): number => { const s = Math.sin(n * 127.3) * 43758.5453; return s - Math.floor(s); }; const CW = 400; const CH = 280; const GAP = 60; const X_L = (1920 - (CW * 3 + GAP * 2)) / 2; // 300 const Y = (1080 - CH) / 2; // 400 const CX = 960; // 主卡中心 const CY = Y + CH / 2; // 540 const IMPACT = 20; // 落点帧 // 冲击波:80→900px 14f out-cubic。前沿到达邻卡中心距 460px 的帧: // (460-80)/820=0.463 → 1-(1-p)^3 → p≈0.19 → t≈2.6f → 取落点后 3f = 帧 23 const HIT_NEIGHBOR = IMPACT + 3; // 14 个粒子:方/圆混合,角度带向上偏置,飞散 160–340px 减速缩小消失 const PARTICLES = Array.from({ length: 14 }).map((_, i) => ({ angle: -Math.PI / 2 + (h(i + 1) - 0.5) * Math.PI * 1.7, // 上半球为主 dist: 160 + h(i + 40) * 180, size: 8 + h(i + 80) * 10, square: i % 2 === 0, })); // 邻卡被推开的阻尼振荡包络:t=0 瞬时到 1,之后余弦衰减弹回,40f 后钳 0 保真静止 const pushEnv = (f: number): number => { const t = f - HIT_NEIGHBOR; if (t < 0 || t >= 40) return 0; return Math.cos(t * 0.5) * Math.exp(-t / 8); }; export const ImpactBurstKit: React.FC = () => { const frame = useCurrentFrame(); // ── 主卡砸落:14–20 帧 scale 1.8→1 / y -120→0,加速进场 const dropP = interpolate(frame, [14, IMPACT], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.cubic), }); const mainScale = interpolate(dropP, [0, 1], [1.8, 1]); const mainDy = interpolate(dropP, [0, 1], [-120, 0]); // 落点后 6f 压扁回弹(squash & stretch) const sq = interpolate(frame, [IMPACT, IMPACT + 3, IMPACT + 6], [0, 1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.quad), }); const mainSx = mainScale * (1 + 0.07 * sq); const mainSy = mainScale * (1 - 0.1 * sq); // ── ① 冲击波环:半径 80→900,14f,opacity 0.75→0 const ringP = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); const ringR = interpolate(ringP, [0, 1], [80, 900]); const ringOp = frame >= IMPACT && frame < IMPACT + 14 ? 0.75 * (1 - ringP) : 0; // ── ② 粒子:落点起 22f,减速飞散 + 缩小 + 淡出 const pT = interpolate(frame, [IMPACT, IMPACT + 22], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); const particlesAlive = frame >= IMPACT && frame < IMPACT + 22; // ── ③ 邻卡外推 30px + rotate ±3°,阻尼弹回 const env = pushEnv(frame); const pushX = 30 * env; const pushRot = 3 * env; // ── ④ 震屏:落点起 4f,6px 衰减(h 伪随机方向,帧确定) let shakeX = 0; let shakeY = 0; if (frame >= IMPACT && frame < IMPACT + 4) { const amp = 6 * (1 - (frame - IMPACT) / 4); shakeX = (h(frame * 3.7) - 0.5) * 2 * amp; shakeY = (h(frame * 7.1 + 13) - 0.5) * 2 * amp; } return (
{/* 左邻卡:被冲击波推开再弹回 */}
{/* 右邻卡 */}
{/* 主卡:砸落 + 落点压扁回弹 */}
{/* ② 粒子迸发(画在卡之上) */} {particlesAlive && PARTICLES.map((p, i) => { const px = CX + Math.cos(p.angle) * p.dist * pT; const py = CY + Math.sin(p.angle) * p.dist * pT; const s = p.size * (1 - pT); if (s < 0.5) return null; return (
); })} {/* ① 冲击波环(最上层扫过邻卡) */} {ringOp > 0 && (
)}
); }; // ===== demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx ===== // 金田透视急停(kanada-perspective-snap)——金田伊功式夸张透视入场。 // 一张卡片以鱼眼级夸张透视姿态高速甩入画面中心:容器 perspective 300→1500px // (短焦→长焦,透视畸变随之收敛),卡片 rotate3d(0.5,1,0.1) 58°→0 + // scale 1.7→1 + translateX -700→0,近角冲出画面感。落定瞬间"啪"地弹平: // rotateY 过冲 +5° 再 4f 回 0;同时 6px 震屏 2f 衰减,拉长斜影收为正常投影。 // 甩入期整卡叠 blur(2px) 增速感,落定即摘(保证收尾逐帧全同)。 // 关键帧:0–18 透视甩入(out cubic)→ 14–18 rotateY 过冲至 +5° → // 18–22 回弹归 0 + 震屏衰减 + 阴影收正 → 22–130 全静止(≥45f)。 import React from 'react'; import { useCurrentFrame, interpolate, Easing } from 'remotion'; import { G, Card, TitleBlock } from '../../_fixtures/Fixtures'; // 确定性伪随机(震屏抖动用) const h = (n: number): number => { const s = Math.sin(n * 127.3) * 43758.5453; return s - Math.floor(s); }; const CLAMP = { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } as const; const CARD_W = 520; const CARD_H = 340; const CX = (1920 - CARD_W) / 2; // 700 const CY = (1080 - CARD_H) / 2; // 370 export const KanadaPerspectiveSnap: React.FC = () => { const frame = useCurrentFrame(); // 0–18f 甩入主通道(out cubic:先猛后缓,急停感) const p = interpolate(frame, [0, 18], [0, 1], { ...CLAMP, easing: Easing.out(Easing.cubic) }); const persp = interpolate(p, [0, 1], [300, 1500]); // 短焦鱼眼→长焦收平 const angle3d = interpolate(p, [0, 1], [58, 0]); // rotate3d(0.5,1,0.1) const scale = interpolate(p, [0, 1], [1.7, 1]); const tx = interpolate(p, [0, 1], [-700, 0]); // rotateY 过冲通道:14–18f 冲到 +5°,18–22f "啪"地回 0 const rotY = frame < 18 ? interpolate(frame, [14, 18], [0, 5], CLAMP) : interpolate(frame, [18, 22], [5, 0], { ...CLAMP, easing: Easing.out(Easing.cubic) }); // 落定震屏:18f 起 6px,2f 内衰减到 0(21f 后恒为 0,保证真静止) const shakeAmp = frame >= 18 ? interpolate(frame, [18, 21], [6, 0], CLAMP) : 0; const shakeX = shakeAmp * (h(frame * 7 + 1) * 2 - 1); const shakeY = shakeAmp * (h(frame * 13 + 2) * 2 - 1); // 阴影:飞行期拉长斜影(大偏移大模糊)→ 落定收为正常投影(18–22f 收拢) const shOff = frame < 18 ? interpolate(p, [0, 1], [1, 0.35]) : interpolate(frame, [18, 22], [0.35, 0], { ...CLAMP, easing: Easing.out(Easing.quad) }); const shX = interpolate(shOff, [0, 1], [0, 70]); const shY = interpolate(shOff, [0, 1], [8, 52]); const shBlur = interpolate(shOff, [0, 1], [14, 36]); const shAlpha = interpolate(shOff, [0, 1], [0.16, 0.3]); // 甩速 blur:透视扭曲期叠 2px,落定(18f)即摘,收尾无逐帧滤镜 const blur = frame < 18 ? 2 : 0; return (
{/* 落点槽位(虚线框,给"甩到哪"一个参照) */}
{/* 透视容器:perspective 随落定从鱼眼收敛到长焦 */}
); }; // ===== demos/effects/slam-entrance-moves/ScoreSlam.tsx ===== // 比分砸落入场(score-slam)——ESPN 比分弹窗 slam。 // KPI 卡从 scale 2.5 / rotate 5° / y-80 高空加速砸落屏心,落点帧同时触发 // 三件套:冲击波描边圆环扩散消散、8 个尘点小方块 seeded 抛物线飞散、 // 整画面震屏指数衰减。砸落带 3% 压缩过冲回弹落定。 // 关键帧:0–8 环境 hold → 8–14 砸落(Easing.in(quad),scale 2.5→0.97, // rotate 5→0,y -80→0)→ 14 落点帧触发环/尘/震 → 14–22 过冲回弹 0.97→1 → // 14–28 圆环 60→720px 直径 op 0.7→0 → 14–30 尘点飞散 → 14–19 震屏 18px 衰减 // → 30–135 全静止(≥45f,无逐帧噪声层)。 import React from 'react'; import { useCurrentFrame, interpolate, Easing } from 'remotion'; import { G, FakeDashboard, TitleBlock } from '../../_fixtures/Fixtures'; // 库规伪随机 const h = (n: number) => { const s = Math.sin(n * 127.3) * 43758.5453; return s - Math.floor(s); }; const IMPACT = 14; // 落点帧 const CX = 960; const CY = 540; const CARD_W = 460; const CARD_H = 260; export const ScoreSlam: React.FC = () => { const frame = useCurrentFrame(); // —— 卡片砸落:8–14 加速下砸到 0.97(压缩过冲),14–22 回弹到 1 —— const slamScale = frame < IMPACT ? interpolate(frame, [8, IMPACT], [2.5, 0.97], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.quad), }) : interpolate(frame, [IMPACT, 22], [0.97, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); const slamRot = interpolate(frame, [8, IMPACT], [5, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.quad), }); const slamY = interpolate(frame, [8, IMPACT], [-80, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.quad), }); const cardOp = interpolate(frame, [8, 11], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); // —— 震屏:落点帧起 5f,18px 指数衰减,seeded 方向,19f 后严格归零 —— let shakeX = 0; let shakeY = 0; if (frame >= IMPACT && frame < IMPACT + 5) { const t = frame - IMPACT; const amp = 18 * Math.exp(-t * 0.9); shakeX = amp * (h(frame * 7 + 1) * 2 - 1); shakeY = amp * (h(frame * 13 + 2) * 2 - 1); } // —— 冲击波圆环:14f 内直径 60→720,opacity 0.7→0 —— const ringT = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); const ringD = interpolate(ringT, [0, 1], [60, 860]); // 不透明度走线性帧时间(与扩散的 out-cubic 解耦),扩到最大前都保持可见 const ringTLin = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const ringOp = interpolate(ringTLin, [0, 0.65, 1], [0.75, 0.55, 0]); const ringOn = frame >= IMPACT && frame < IMPACT + 14; // —— 8 个尘点:seeded 角度抛物线飞散 120–260px,16f 减速缩小消失 —— const dustT = interpolate(frame, [IMPACT, IMPACT + 16], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); const dustTLin = interpolate(frame, [IMPACT, IMPACT + 16], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const dustOn = frame >= IMPACT && frame < IMPACT + 16; return (
{/* 震屏容器:整画面一起抖 */}
{/* 虚化 dashboard 环境底 */}
{/* ② 尘点飞散 */} {dustOn && Array.from({ length: 8 }).map((_, i) => { const ang = (i / 8) * Math.PI * 2 + (h(i + 3) - 0.5) * 0.7; const dist = 160 + h(i + 11) * 160; // 160–320px const size = 18 + h(i + 23) * 12; // 18–30px const dx = Math.cos(ang) * dist * dustT; // 抛物线:先随角度飞出,再叠加重力下坠 const dy = Math.sin(ang) * dist * dustT + 90 * dustT * dustT; const s = size * (1 - 0.75 * dustTLin); const op = interpolate(dustTLin, [0, 0.75, 1], [0.9, 0.7, 0]); return (
); })} {/* ① 冲击波圆环 */} {ringOn && (
)} {/* KPI 卡本体 */} {frame >= 8 && (
+247%
QUARTERLY GROWTH
)}
); };