Motion · Dados
Particle sand-fill bars
Bar charts don't grow tall — they "rain into being": square particles fall one by one and pile up into bars, then solidify.
Fonte: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composição Remotion (React/TSX) — vídeo de prévia, sem player no navegador
When to use
Bar chart / magnitude-comparison entrance; data sections about "accumulation/convergence" semantics
Intention
Swaps the bar-chart entrance from a "growing taller" animation to "falling accumulation": a rain of square particles falls above each bar, falling one by one under gravity acceleration, stopping and bouncing back 15% the instant they hit the pile, layering up until the target height is reached, then the particle surface fades out and is replaced by a solid bar with the value label popping up top. The data's "quantity" becomes something you can see "being piled up one grain at a time" — division of labor with unit-dot-swarm-regroup (the same batch of dots changing formation): that one is dots reshaping in a plane, this is falling material building up a shape.
Duration & energy
- Duration: 4–5s
- Energy: Medium-high (a constructive-feeling entrance)
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
把柱状图入场从"长高动画"换成"落体堆积":每根柱上方下一场方块雨, 粒子逐颗坠落(重力加速)、触堆积面即停+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)
Reference implementation (Remotion)
- demos/data/particle-sand-fill/ParticleSandFill.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/data/particle-sand-fill/ParticleSandFill.tsx
The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.
Tudo nesta página também é legível por máquina. Aponte seu agente para o JSON ou adicione o servidor MCP do gimgs e peça para buscar templates de motion.
- Baixe o JSON — contém a receita do plano (timing, easing, tabela de parâmetros, armadilhas) e o código TSX completo.
- Clone o repositório video-shotcraft; o componente importa fixtures de demos/_fixtures e helpers de assets/lib.
- Registre o componente como <Composition> em um projeto Remotion e renderize com `npx remotion render`, ou reimplemente a receita no seu próprio engine.
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 "Particle sand-fill bars" (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 |
|---|---|---|---|
| — | |||
Mais nesta categoria

Timeline travel pan
Timeline travel — the camera accelerates along a horizontal ruler axis past version tick marks, a card springs upright and pauses briefly at each tick, and an abrupt stop pushes in on the final tick.

Scroll brake moves
A long scroll decelerates to a precise stop on the latest item; a variant adds a reticle lock on the same frame.

Ring diagram annotation reveal
A full-screen subject is squeezed into a ring diagram by a circular window, then the group shrinks left to reveal annotations.

Particle celebration hits
Confetti cannons fire from both sides, or sparks fizz off the counter each time it ticks past a thousand.