Motion · Ánh sáng & nhấn
Đập vào cảnh năng lượng cao
Ba kiểu đập vào cảnh năng lượng cao: dừng phối cảnh kiểu Kanada, đập rơi tỷ số, hoặc lan chấn động sang thẻ lân cận.
Nguồn: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composition Remotion (React/TSX) — có video preview, không có player trong trình duyệt
Biến thể
perspective 300→1500px + rotate3d 58°→0 + scale 1.7→1 lao vào trong 18f, 4f cuối vọt quá +5° rồi bật phẳng, bóng chéo dài thu về thẳng.
Màn ra mắt phóng khoáng cho một thẻ đơn; tông thiên về hoạt hình/nhẹ nhàng
Thẻ rơi từ scale 2.5/rotate 5° trong sáu khung hình với Easing.in(quad); ở khung hình hạ cánh, vòng tròn lan tỏa + bụi bay tán loạn + rung màn hình cùng xảy ra trên một khung hình.
Màn ra mắt hạng nặng cho số liệu KPI/kiểu tỷ số
Bộ ba của B, cộng thêm đầu sóng chấn động được tính chính xác theo bán kính-khoảng cách quét qua khung hình của các thẻ lân cận, đẩy chúng ra ngoài 30px kèm xoay ±3° rồi bật lại có giảm chấn.
"Cả màn hình rung chuyển" khi trên trang đã có các thẻ lân cận
Khi nào dùng
Màn ra mắt đấm mạnh cho thẻ chủ lực/thẻ KPI; impact-feedback lo phản hồi sau khi an vị, thẻ này lo chính bản thân màn ra mắt là một cú va chạm
Ý đồ
Từ vựng vào cảnh trong kho từ trước đến giờ đỉnh cao vẫn là "phát bài" của deck-deal-flyin — nhanh nhưng không nặng. Cảnh này bổ sung mức "đập", với ba vị: A là cảm giác định hướng — thẻ lao vào với phối cảnh mắt cá phóng đại sát ống kính rồi "bốp" bật phẳng, sự phóng khoáng của phong cách hoạt hình Kanada; B là cảm giác trọng lượng — thẻ KPI rơi vào từ kích cỡ 2.5 lần trên màn hình, vòng tròn lan tỏa + bụi bay + rung màn hình cùng nổ trên một khung hình, sức nặng của một popup bảng tỷ số thể thao; C là cảm giác lan truyền — trên nền bộ ba của B, đầu sóng chấn động quét qua các thẻ lân cận trái phải và đẩy chúng ra thấy rõ rồi bật lại, "cú này rung cả hàng xóm" — một việc, nói rõ độ mạnh theo ba cách. Chọn theo nhu cầu kể chuyện: A nói "nó đã đến", B nói "nó rất nặng", C nói "nó rung cả sân".
Thời lượng & năng lượng
- Thời lượng: Mỗi kiểu: đoạn động tác 6–22f + dư chấn va chạm ~16f + giữ từ 45f trở lên
- Năng lượng: Cao
Công thức cảnh quay (bản gốc tiếng Trung — timing, easing, bảng tham số, lỗi hay gặp)
意图
库内入场词汇的力度上限一直是 deck-deal-flyin 的"发牌"——快但不重。 本卡补"砸"这一档,三种口味:A 是方向感——卡片带鱼眼级夸张透视贴着 镜头甩进来,"啪"地弹平,动漫金田流的潇洒;B 是重量感——KPI 卡从 镜头前 2.5 倍大小砸落,圆环+尘点+震屏三件套同帧起爆,体育比分弹窗 的分量;C 是传导感——B 的三件套之上,冲击波前沿扫过左右邻卡时把 它们可见地推开再弹回,"这一下震到了邻居",一件事说清力量大小。 选型按叙事需要:告诉观众"它来了"用 A,"它很重"用 B,"它震动了 全场"用 C。
三式选型
| 式 | 做法 | 适用 | |----|------|------| | A kanada-perspective-snap | perspective 300→1500px + rotate3d 58°→0 + scale 1.7→1 甩入 18f,末 4f 过冲 +5° 弹平,长斜影收正 | 单卡潇洒亮相;调性偏动漫/轻快 | | B score-slam | 卡从 scale 2.5/rotate 5° 六帧 Easing.in(quad) 砸落,落点帧圆环扩散+尘点飞散+震屏同帧 | KPI/比分类重磅数字登场 | | C impact-burst-kit | B 三件套 + 冲击波前沿按半径-距离精算扫过邻卡帧,邻卡外推 30px + rotate ±3° 阻尼弹回 | 页面上已有邻居时的"全场震动" |
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 甩入 | 18f Easing.out(cubic),translateX -700→0 与透视变形同跑;途中 blur 2px 落定即摘 | 快于 12f 读不到鱼眼畸变,慢于 24f 泄劲 | | A 落定 | rotateY 过冲 +5° 4f 回 0 + 6px 震屏 2f + 斜长影收正常投影 | "啪"在过冲回弹那一下,不在位移结束 | | B 砸落 | 6f Easing.in(quad)(加速砸,不是减速落) | ease-out 落地是"放下",ease-in 才是"砸" | | B 三件套 | 圆环 80→860px/14f + 尘点 18–30px 飞 160–320px + 震屏 18px 指数衰减 4f | 原案(720px/10px 尘/8px 震)全档不可感,实渲已 ×1.5–2 | | B 解耦命门 | 环/尘的"扩散"用 out-cubic、"消散"用线性帧时间 | 同用 out-cubic 前段跑太快,刚扩开就淡没 | | C 波及帧 | 前沿过邻卡中心距反解(460px 距 ≈ 落点后 3f);包络 cos(t/2)·e^(−t/8),40f 硬钳 0 | 邻卡与主卡同帧动读作整版抖动,晚 3f 才是"被波及" | | 收尾 | 全部余波归零后真静止 ≥45f | 重拳 hold 按平常两倍给(同 beat-cut B 判例) |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 三式都带震屏,震屏族(impact-feedback、montage-rhythm A/C)同一镜头
只能有一个震源;三式互相之间同镜头也只能选一
- 声音强依赖:A 的"啪"、B/C 的音爆必须同帧钉(sound-design §4.5),
无声版 B/C 的三件套读作"卡在抽搐"
- C 式邻卡必须先驻场稳定 ≥15f 再被波及——邻卡自己还在入场时被推开,
因果链读不出来
- 与 domino-cascade(montage-rhythm C 式)的区别要守住:domino 是
连锁"接力入场"(邻居被撞后自己入场),C 式是"被波及晃一下回原位" (邻居早已在场)——别混用在同一组元素上
- 全片砸入 ≤2 次;三次以上每次都响等于都不响(P4)
参考实现
demos/effects/slam-entrance-moves/ (ImpactBurstKit.tsx / KanadaPerspectiveSnap.tsx / ScoreSlam.tsx)
Mã nguồn tham chiếu (Remotion)
- demos/effects/slam-entrance-moves/ImpactBurstKit.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx
- demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx
- demos/effects/slam-entrance-moves/ScoreSlam.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ScoreSlam.tsx
Component import fixture chung từ demos/_fixtures và helper từ assets/lib của repo; xem SKILL.md trong repo để biết quy trình render.
Mọi thứ trên trang này đều có bản máy đọc. Đưa agent link JSON, hoặc thêm MCP server gimgs rồi bảo nó tìm motion template.
- Tải JSON — trong đó có công thức cảnh quay (timing, easing, bảng tham số, lỗi hay gặp) và toàn bộ source TSX.
- Clone repo video-shotcraft; component import fixture chung từ demos/_fixtures và helper từ assets/lib.
- Đăng ký component thành <Composition> trong project Remotion rồi render bằng `npx remotion render`, hoặc dựng lại công thức bằng engine của bạn.
curl -s https://gimgs.net/motion/slam-entrance-moves.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/effects/slam-entrance-moves/ImpactBurstKit.tsx, demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx, demos/effects/slam-entrance-moves/ScoreSlam.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("slam-entrance-moves")Prompt
Use the video-shotcraft shot recipe "Đập vào cảnh năng lượng cao" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/slam-entrance-moves.json Raw TSX: https://gimgs.net/motion/slam-entrance-moves/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/effects/slam-entrance-moves/ImpactBurstKit.tsx, demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx, demos/effects/slam-entrance-moves/ScoreSlam.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 4.39s 1. Read the JSON and follow its "prompt" section (when to use, intention, parameter table, known pitfalls). 2. Adapt it for: <describe what you want>. 3. Either clone the repo, register the component as a <Composition> in a Remotion project and run npx remotion render <CompositionId> out.mp4 — or re-implement the recipe in your own engine (HTML/WAAPI works: keep the same timing and easing).
// ===== 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 (
<div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', inset: 0, transform: `translate(${shakeX}px, ${shakeY}px)` }}>
<div style={{ position: 'absolute', left: 120, top: 96 }}>
<TitleBlock text="IMPACT BURST KIT" size={54} />
</div>
{/* 左邻卡:被冲击波推开再弹回 */}
<div
style={{
position: 'absolute',
left: X_L,
top: Y,
transform: `translateX(${-pushX}px) rotate(${-pushRot}deg)`,
}}
>
<Card w={CW} h={CH} seed={2} />
</div>
{/* 右邻卡 */}
<div
style={{
position: 'absolute',
left: X_L + (CW + GAP) * 2,
top: Y,
transform: `translateX(${pushX}px) rotate(${pushRot}deg)`,
}}
>
<Card w={CW} h={CH} seed={4} />
</div>
{/* 主卡:砸落 + 落点压扁回弹 */}
<div
style={{
position: 'absolute',
left: X_L + CW + GAP,
top: Y + mainDy,
transform: `scale(${mainSx}, ${mainSy})`,
transformOrigin: '50% 100%',
}}
>
<Card w={CW} h={CH} seed={7} style={{ boxShadow: '0 6px 18px rgba(0,0,0,0.16)' }} />
</div>
{/* ② 粒子迸发(画在卡之上) */}
{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 (
<div
key={i}
style={{
position: 'absolute',
left: px - s / 2,
top: py - s / 2,
width: s,
height: s,
background: G.ink,
borderRadius: p.square ? 2 : '50%',
opacity: 1 - pT * pT,
}}
/>
);
})}
{/* ① 冲击波环(最上层扫过邻卡) */}
{ringOp > 0 && (
<div
style={{
position: 'absolute',
left: CX - ringR,
top: CY - ringR,
width: ringR * 2,
height: ringR * 2,
border: `3px solid ${G.ink}`,
borderRadius: '50%',
opacity: ringOp,
boxSizing: 'border-box',
}}
/>
)}
</div>
</div>
);
};
// ===== 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 (
<div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', left: shakeX, top: shakeY, width: 1920, height: 1080 }}>
<div style={{ position: 'absolute', left: 120, top: 96 }}>
<TitleBlock text="KANADA PERSPECTIVE SNAP" size={54} />
</div>
{/* 落点槽位(虚线框,给"甩到哪"一个参照) */}
<div
style={{
position: 'absolute',
left: CX - 20,
top: CY - 20,
width: CARD_W + 40,
height: CARD_H + 40,
border: `3px dashed ${G.bar}`,
borderRadius: 22,
boxSizing: 'border-box',
}}
/>
{/* 透视容器:perspective 随落定从鱼眼收敛到长焦 */}
<div style={{ position: 'absolute', left: CX, top: CY, perspective: `${persp}px`, perspectiveOrigin: '30% 50%' }}>
<div
style={{
transform: `translateX(${tx}px) scale(${scale}) rotate3d(0.5, 1, 0.1, ${angle3d}deg) rotateY(${rotY}deg)`,
transformOrigin: '20% 50%',
filter: `drop-shadow(${shX}px ${shY}px ${shBlur}px rgba(0,0,0,${shAlpha}))${blur ? ` blur(${blur}px)` : ''}`,
}}
>
<Card w={CARD_W} h={CARD_H} seed={3} />
</div>
</div>
</div>
</div>
);
};
// ===== 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 (
<div
style={{
width: 1920,
height: 1080,
background: G.bg,
position: 'relative',
overflow: 'hidden',
}}
>
{/* 震屏容器:整画面一起抖 */}
<div
style={{
position: 'absolute',
inset: 0,
transform: `translate(${shakeX}px, ${shakeY}px)`,
}}
>
{/* 虚化 dashboard 环境底 */}
<div
style={{
position: 'absolute',
left: 0,
top: 0,
filter: 'blur(6px)',
transform: 'scale(1.05)',
transformOrigin: '50% 50%',
}}
>
<FakeDashboard variant="B" />
</div>
<div style={{ position: 'absolute', left: 120, top: 96 }}>
<TitleBlock text="SCORE SLAM" size={54} />
</div>
{/* ② 尘点飞散 */}
{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 (
<div
key={i}
style={{
position: 'absolute',
left: CX + dx - s / 2,
top: CY + CARD_H / 2 - 20 + dy - s / 2,
width: s,
height: s,
background: G.ink,
opacity: op,
borderRadius: 2,
}}
/>
);
})}
{/* ① 冲击波圆环 */}
{ringOn && (
<div
style={{
position: 'absolute',
left: CX - ringD / 2,
top: CY - ringD / 2,
width: ringD,
height: ringD,
borderRadius: '50%',
border: `6px solid ${G.ink}`,
opacity: ringOp,
boxSizing: 'border-box',
}}
/>
)}
{/* KPI 卡本体 */}
{frame >= 8 && (
<div
style={{
position: 'absolute',
left: CX - CARD_W / 2,
top: CY - CARD_H / 2,
width: CARD_W,
height: CARD_H,
background: G.card,
border: `3px solid ${G.border}`,
borderRadius: 18,
boxShadow: '0 10px 30px rgba(0,0,0,0.18)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
opacity: cardOp,
transform: `translateY(${slamY}px) rotate(${slamRot}deg) scale(${slamScale})`,
transformOrigin: '50% 50%',
}}
>
<div
style={{
fontFamily: 'Helvetica, Arial, sans-serif',
fontWeight: 800,
fontSize: 110,
color: G.ink,
letterSpacing: -3,
lineHeight: 1,
}}
>
+247%
</div>
<div
style={{
fontFamily: 'Helvetica, Arial, sans-serif',
fontWeight: 600,
fontSize: 26,
color: G.mid,
letterSpacing: 4,
}}
>
QUARTERLY GROWTH
</div>
</div>
)}
</div>
</div>
);
};
- Read the recipePrompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
- Get the codetool: git
git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install - Register + rendertool: remotion
npx remotion render <CompositionId> out.mp4 # component: demos/effects/slam-entrance-moves/ImpactBurstKit.tsx, demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx, demos/effects/slam-entrance-moves/ScoreSlam.tsx - Or re-implementPort the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
| param | type | default | description |
|---|---|---|---|
| — | |||
Cùng danh mục

Đèn rọi hiện hình trên nền tối
Ba kiểu đèn rọi hiện hình trên nền tối — A quét thức-ngủ (đèn tới thì sáng, đèn đi thì tối), B quét ngang rìa viền (ánh tím bám mép UI thấm vào + đèn quét phải đều), C hiện hình đều từ góc (đèn hình tròn nở đều từ góc sáng cả màn hình); ánh sáng chính là lời kể trên nền đen.

Vệt sáng quét lắp ráp bay vào
Vệt sáng quét từ trên xuống lưới nền tối trống, tới đâu thành phần vùng đó bay vào khớp vị trí — quét xong là lắp ráp xong.

Vệt sáng quét chú thích tiêu điểm
Một vệt sáng quét từ trên xuống dưới trang, khoanh khung ngắm kiểu máy ảnh vào từng vùng nó đi qua rồi gắn chú thích chữ mono.

Khung quét quét đọc tài liệu
Tài liệu khung xương bật vào giữa, ngoặc chữ L rơi xuống bốn góc, một vệt sáng quét qua lại 5 lượt trong khi tài liệu đứng yên.