Motion · Dữ liệu
Cột số liệu lấp hạt cát
Biểu đồ cột không mọc cao dần mà "đổ mưa ra": hạt vuông rơi từng cái chất đống thành cột, đầy thì đông đặc lại.
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
Khi nào dùng
Màn vào cảnh biểu đồ cột/so sánh quy mô; đoạn dữ liệu kể ngữ nghĩa "tích lũy/hội tụ"
Ý đồ
Đổi màn vào cảnh biểu đồ cột từ hoạt ảnh "mọc cao dần" sang "chất đống rơi": phía trên mỗi cột đổ một trận mưa hạt vuông, hạt rơi từng cái theo gia tốc trọng lực, chạm mặt đống thì dừng và nảy lại 15%, chồng lớp này lên lớp kia cho tới khi đạt chiều cao mục tiêu, rồi bề mặt hạt mờ dần đi để nhường chỗ cho cột đặc, nhãn giá trị bật lên ở đỉnh. "Lượng" của dữ liệu trở thành thứ nhìn thấy được "được góp từng hạt một" — phân công với unit-dot-swarm-regroup (cùng một đám điểm đổi đội hình): đó là các điểm đổi hình trên mặt phẳng, đây là vật liệu rơi xây thành hình khối.
Thời lượng & năng lượng
- Thời lượng: 4–5s
- Năng lượng: Trung bình cao (màn vào cảnh mang cảm giác xây dựng)
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)
意图
把柱状图入场从"长高动画"换成"落体堆积":每根柱上方下一场方块雨, 粒子逐颗坠落(重力加速)、触堆积面即停+15% 回弹,一层压一层堆到目标 高度后粒子面淡出换实体柱、顶部数值标签弹出。数据的"量"变成看得见的 "一颗一颗攒出来"——与 unit-dot-swarm-regroup(点群平面迁徙聚形) 分工:那是同一批点改队形,这是落体材料筑成图形。
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 粒径 | 14px 方点 | 词汇原案 4px 不可感,QA 中加码 3.5 倍 | | 每柱粒子数 | ~50 颗 | 太少堆积感稀疏,太多单颗不可辨 | | 坠落 | 重力加速,落地帧闭式预解析 | 堆积高度=已落数×粒径,禁真碰撞模拟 | | 回弹 | 触面 15% 单次回弹 | 无回弹读作贴图;多次回弹拖节奏 | | 柱间错峰 | 6f 逐柱启动 | 齐落读作机械;错峰有"雨势推移"感 | | 收束 | 堆满→粒子面淡出→实体柱+标签 back-out 弹出 | 粒子态直接定格显脏,必须凝成实体 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 落地帧全部闭式预解析(每颗粒子的落点/落帧编译期可算),
帧确定性是渲染正确的前提——禁逐帧状态累积
- 只适合 3–5 根柱的量级对比;十几根柱的密集图表用常规入场
(雨帘太多读作噪声)
- 与"碎块解体"否决判例的界限:这是构筑(从无到有堆出图形),
不是破坏(把完整图形打碎)——方向语义相反,别做反向播放变体
- 声音:细密颗粒落地声铺底,每柱堆满一声轻"叩",标签弹出一声 tick
参考实现
demos/data/particle-sand-fill/ (ParticleSandFill.tsx)
Mã nguồn tham chiếu (Remotion)
- demos/data/particle-sand-fill/ParticleSandFill.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/data/particle-sand-fill/ParticleSandFill.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/particle-sand-fill.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/data/particle-sand-fill/ParticleSandFill.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("particle-sand-fill")Prompt
Use the video-shotcraft shot recipe "Cột số liệu lấp hạt cát" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/particle-sand-fill.json Raw TSX: https://gimgs.net/motion/particle-sand-fill/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/data/particle-sand-fill/ParticleSandFill.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 5.38s 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/data/particle-sand-fill/ParticleSandFill.tsx =====
// particle-sand-fill —— 粒子落斗成柱
// 图表卡内 4 根柱,每根柱上方"下雨":14px 方点错峰坠落(重力加速),触堆积面即停
// + 15% 回弹一下,逐层堆高——堆积高度闭式预解析(第 k 层顶面 = 基线 - (k+1)×粒径,
// 无真碰撞)。各柱错峰 6f 启动;堆满后粒子面淡出换实体柱 + 顶部数值标签弹出。
// 结尾全部粒子条件卸载、只剩实体柱 + 标签,真静止 ≥35f。
// 帧确定性:sin 散列派生每颗出发帧抖动/起点错高,落地帧由高度差闭式反解。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, TitleBlock } from '../../_fixtures/Fixtures';
const AMBER = '#b45309';
const frac = (x: number) => x - Math.floor(x);
const rnd = (i: number, salt: number) => frac(Math.sin(i * 12.9898 + salt * 78.233) * 43758.5453);
const CARD = { x: 460, y: 300, w: 1000, h: 560 };
const PLOT_BOTTOM = CARD.y + CARD.h - 70; // 堆积地面(卡内基线)
const GRAIN = 14; // 方点边长(宁大勿小:4px 在 1080p 卡内不可感,加码到 14)
const PER_LAYER = 9; // 每层 9 颗 → 柱宽 126px
const BAR_W = GRAIN * PER_LAYER;
const DROP_FROM = 230; // 距各自落点上方 ~230px 起落
const GRAV = 1.6; // px/f²
const STAGGER = 6; // 各柱错峰启动
const RATE = 0.28; // 颗间出发间隔(帧)——最高柱 216 颗需 ~60f 发完,全局 f120 内收束
const BARS = [
{ cx: CARD.x + 175, h: 238, label: '238' },
{ cx: CARD.x + 395, h: 336, label: '336' },
{ cx: CARD.x + 615, h: 182, label: '182' },
{ cx: CARD.x + 835, h: 294, label: '294' },
].map((b) => ({ ...b, layers: Math.round(b.h / GRAIN), n: Math.round(b.h / GRAIN) * PER_LAYER }));
const fallTime = (dist: number) => Math.sqrt((2 * dist) / GRAV);
const departOf = (bar: number, i: number) => 8 + bar * STAGGER + i * RATE + rnd(i, bar * 7 + 1) * 1.5;
export const ParticleSandFill: React.FC = () => {
const frame = useCurrentFrame();
return (
<div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 110, width: '100%', textAlign: 'center' }}>
<TitleBlock text="PARTICLE SAND FILL" size={72} />
</div>
{/* 图表卡 */}
<div style={{
position: 'absolute', left: CARD.x, top: CARD.y, width: CARD.w, height: CARD.h,
background: G.card, border: `2px solid ${G.border}`, borderRadius: 14,
boxSizing: 'border-box', padding: 28, boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
}}>
<div style={{ height: 14, width: 260, background: G.bar, borderRadius: 7 }} />
<div style={{ height: 10, width: 160, background: G.line, borderRadius: 5, marginTop: 10 }} />
</div>
{/* 基线 */}
<div style={{ position: 'absolute', left: CARD.x + 40, top: PLOT_BOTTOM, width: CARD.w - 80, height: 3, background: G.line }} />
{BARS.map((bar, b) => {
const left = bar.cx - BAR_W / 2;
// 末颗落地帧(闭式):末颗落点在堆顶,坠距仍 ≈DROP_FROM
const lastLand = departOf(b, bar.n - 1) + fallTime(DROP_FROM);
const doneAt = lastLand + 7; // 回弹收完 → 开始交接
const solidOp = interpolate(frame, [doneAt, doneAt + 10], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
const labelScale = interpolate(frame, [doneAt + 6, doneAt + 18], [0, 1], {
easing: Easing.out(Easing.back(2.2)),
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
return (
<React.Fragment key={b}>
{solidOp > 0 && (
<div style={{
position: 'absolute', left, top: PLOT_BOTTOM - bar.h,
width: BAR_W, height: bar.h, background: b === 1 ? AMBER : G.bar,
borderRadius: '6px 6px 0 0', opacity: solidOp,
}} />
)}
{labelScale > 0 && (
<div style={{
position: 'absolute', left: bar.cx - 70, top: PLOT_BOTTOM - bar.h - 62, width: 140,
textAlign: 'center', fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 800,
fontSize: 46, color: b === 1 ? AMBER : G.ink,
transform: `scale(${labelScale})`,
}}>
{bar.label}
</div>
)}
{/* 粒子面:交接完成(solidOp=1)即整体条件卸载 → 真静止 */}
{solidOp < 1 && Array.from({ length: bar.n }).map((_, i) => {
const depart = departOf(b, i);
const age = frame - depart;
if (age <= 0) return null;
const layer = Math.floor(i / PER_LAYER);
const col = i % PER_LAYER;
const targetTop = PLOT_BOTTOM - (layer + 1) * GRAIN; // 闭式堆积面
const startTop = targetTop - DROP_FROM - rnd(i, b * 13 + 3) * 70;
const dist = targetTop - startTop;
const tLand = fallTime(dist);
let top: number;
if (age < tLand) {
top = startTop + 0.5 * GRAV * age * age;
} else {
const ba = age - tLand;
const bounce = ba < 6 ? Math.sin((ba / 6) * Math.PI) * GRAIN * 2 * 0.15 * (1 + rnd(i, b * 13 + 9)) : 0;
top = targetTop - bounce;
}
const amber = b === 1 || rnd(i, b * 13 + 7) < 0.18;
return (
<div key={i} style={{
position: 'absolute', left: left + col * GRAIN + 1, top, width: GRAIN - 2, height: GRAIN - 2,
background: amber ? AMBER : G.mid, opacity: 1 - solidOp,
borderRadius: 2,
}} />
);
})}
</React.Fragment>
);
})}
</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/data/particle-sand-fill/ParticleSandFill.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

Lia dọc trục thời gian
Lia dọc trục thời gian — máy quay tăng tốc dọc theo trục thước ngang lướt qua các mốc phiên bản, mỗi mốc thẻ bật đứng dừng ngắn, mốc cuối phanh gấp đẩy máy vào.

Cuộn phanh gấp danh sách dài
Hai kiểu phanh cuộn dài: changelog-scroll-brake giảm tốc dừng chính xác; brake-reticle-lock thêm khóa tâm ngắm trên cùng khung hình.

Vòng đồng tâm hé lộ chú thích
Chủ thể toàn màn hình bị khung tròn thu lại thành sơ đồ vòng đồng tâm, rồi cả nhóm dịch trái thu nhỏ để lộ chú thích.

Va chạm ăn mừng bằng hạt
Hai kiểu hạt ăn mừng: confetti-crossfire bắn pháo hai bên, và counter-tick-sparks bắn tia lửa mỗi khi bộ đếm vượt mốc nghìn.