Motion · Dados
Gauge readout moves
Two gauge-readout styles: needle-sweep-selftest, a full-arc self-test sweep before settling to the true value, and tape-scroll-fixed-pointer, a fixed pointer with a scrolling tape that sprints then brakes.
Fonte: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composição Remotion (React/TSX) — vídeo de prévia, sem player no navegador
Variantes
指针去程 ~12f ease-out 甩满弧,回程 ~20f 带 5-8° 过冲回摆落真值;多表错峰 3-5f;落定同帧盘下数值弹出
dashboard 开场/系统就绪仪式
长刻度带 translate:慢爬段→45px/f 冲刺 ~25f→spring 刹车过冲回摆停位;窗内读数同步刷新
单指标大跳变(性能翻倍/配额提升)
When to use
For a dashboard opening ritual / performance-metric reveal; use A for a multi-gauge power-on feel, B for a single metric's big jump.
Intention
Turn "reporting a value" into a gauge's physical action: A is a car's ignition self-test — the needle first sweeps across the full arc (270°) then settles back to the real value, multiple gauges staggered into a wave, showing the range before the number, a launch-declaration feel; B follows an airspeed-tape grammar — the needle/viewport stays pinned dead center while the ruled tape scrolls past underneath; on a big jump the tape sprints at 45px/f then brakes with a spring, overshooting and rocking back — "the world moves, the needle doesn't", turning the size of a number into a visible distance traveled. Division of labor with odometer-digit-roll: that one rolls digits inside a window, B is the whole ruled scale line panning.
Duration & energy
- Duration: A: 4–5s / B: 4–5s
- Energy: Medium-high (mechanical-ritual type)
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
把"报数值"做成仪表的身体动作:A 是汽车点火自检——表盘指针先"唰"地 甩满全弧(270°)再回落定格真实值,多表错峰成波浪,先亮量程再报数, 开场宣誓感;B 是飞机空速带语法——指针/取景窗钉死画面正中,带刻度的 标尺整体从底下滚过,大数值跳变时刻度带 45px/f 冲刺、末尾 spring 刹车 带回摆过冲,"世界动、针不动",数值大小变成看得见的位移距离。 与 odometer-digit-roll 分工:那是数字在窗里滚,B 是连刻度格线全在平移。
两式选型
| 式 | 做法 | 适用 | |----|------|------| | A needle-sweep-selftest | 指针去程 ~12f ease-out 甩满弧,回程 ~20f 带 5–8° 过冲回摆落真值;多表错峰 3–5f;落定同帧盘下数值弹出 | dashboard 开场/系统就绪仪式 | | B tape-scroll-fixed-pointer | 长刻度带 translate:慢爬段→45px/f 冲刺 ~25f→spring 刹车过冲回摆停位;窗内读数同步刷新 | 单指标大跳变(性能翻倍/配额提升) |
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 甩弧幅度 | 满量程 270° | 只甩到真值=没有自检仪式感;满弧再回落才是"先亮量程" | | A 过冲 | 回落带 5–8° 回摆 | 无过冲读作电子表;过冲=机械指针的质量 | | A 错峰 | 3–5f/表 | 齐甩读作复制粘贴;错峰成波浪 | | B 冲刺速度 | 45px/f(大跳变段) | 慢了读作匀速爬升,"世界动"不成立 | | B 刹车 | spring 过冲(冲过头再弹回停位) | 词汇原案的回摆是"哐"的来源,直停可惜 | | 真静止 | 停位后 ≥30f 全锁死 | 指针类最忌尾漂 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 同批 ring-close-overfill(合环过盈)改改再看无附言——环形进度类
待重做,别急着当第三式并入
- A 的表盘是自绘 SVG(弧刻度+指针),实战贴真 dashboard 时优先找
页面里现成的 gauge 组件特写,没有再造
- 声音:A 甩针一声"唰"+落定"嗒"(多表错峰各一声);B 冲刺段
低频滚动声、刹车一声闷"哐"
参考实现
demos/data/gauge-readout-moves/ (NeedleSweepSelftest.tsx / TapeScrollFixedPointer.tsx)
Reference implementation (Remotion)
- demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx
- demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/data/gauge-readout-moves/TapeScrollFixedPointer.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/gauge-readout-moves.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx, demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("gauge-readout-moves")Prompt
Use the video-shotcraft shot recipe "Gauge readout moves" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/gauge-readout-moves.json Raw TSX: https://gimgs.net/motion/gauge-readout-moves/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx, demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 4.72s 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/gauge-readout-moves/NeedleSweepSelftest.tsx =====
// needle-sweep-selftest —— 满弧扫针自检
// 卡片内三个并排 270° 表盘。f12 "点火"后指针错峰 4f 依次从 0 甩满全弧
// (去程 12f ease-out),再回落到各自真实值(回程 20f:先落过头 8° 再回摆),
// 落定同帧盘下数值文字弹出。三表错峰形成波浪。f60 后真静止 80f。
// 帧确定性:纯 interpolate 分段,全部 clamp,settle 后每帧输出常数。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, TitleBlock } from '../../_fixtures/Fixtures';
const AMBER = '#b45309';
const RED = '#7c2d12';
const CARD_W = 1500;
const CARD_H = 640;
const CARD_X = (1920 - CARD_W) / 2;
const CARD_Y = 300;
const GA_W = CARD_W / 3; // 每表盘占位宽
const R = 148;
const CX = GA_W / 2;
const CY = 240;
// 表盘角 d∈[0,270] → SVG 角 a = 135 + d(0=右,顺时针,y 向下)
const polar = (a: number, r: number): [number, number] => [
CX + r * Math.cos((a * Math.PI) / 180),
CY + r * Math.sin((a * Math.PI) / 180),
];
const arcPath = (d0: number, d1: number, r: number): string => {
const [x0, y0] = polar(135 + d0, r);
const [x1, y1] = polar(135 + d1, r);
const large = d1 - d0 > 180 ? 1 : 0;
return `M ${x0.toFixed(2)} ${y0.toFixed(2)} A ${r} ${r} 0 ${large} 1 ${x1.toFixed(2)} ${y1.toFixed(2)}`;
};
// 三表:点火 f12,错峰 4f
const GAUGES = [
{ start: 12, target: 190 },
{ start: 16, target: 120 },
{ start: 20, target: 235 },
];
const needleAngle = (frame: number, s: number, target: number): number => {
if (frame <= s) return 0;
if (frame <= s + 12) {
// 去程:甩满全弧,ease-out(起步猛)
return interpolate(frame, [s, s + 12], [0, 270], {
easing: Easing.out(Easing.cubic),
});
}
if (frame <= s + 25) {
// 回程主段:落过真实值 8°
return interpolate(frame, [s + 12, s + 25], [270, target - 8], {
easing: Easing.inOut(Easing.cubic),
});
}
// 回摆:从过冲位弹回真实值,之后 clamp 为常数
return interpolate(frame, [s + 25, s + 32], [target - 8, target], {
easing: Easing.out(Easing.cubic),
extrapolateRight: 'clamp',
});
};
const Gauge: React.FC<{ start: number; target: number }> = ({ start, target }) => {
const frame = useCurrentFrame();
const d = needleAngle(frame, start, target);
const settle = start + 32;
const value = Math.round((target / 270) * 100);
const popScale = interpolate(frame, [settle, settle + 4, settle + 8], [0.3, 1.18, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const popOp = interpolate(frame, [settle, settle + 3], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const [tipX, tipY] = polar(135, R - 26);
const [tailX, tailY] = polar(315, 36);
// 大刻度 11 根(每 27°),小刻度每 9°
const ticks: React.ReactNode[] = [];
for (let k = 0; k <= 30; k++) {
const dd = k * 9;
const major = k % 3 === 0;
const a = 135 + dd;
const [x0, y0] = polar(a, R - 8);
const [x1, y1] = polar(a, major ? R - 30 : R - 19);
ticks.push(
<line key={k} x1={x0} y1={y0} x2={x1} y2={y1}
stroke={dd >= 225 ? RED : G.mid} strokeWidth={major ? 4 : 2} />
);
}
return (
<div style={{ width: GA_W, height: 480, position: 'relative' }}>
<svg width={GA_W} height={430}>
{/* 弧形轨道 + 红区 */}
<path d={arcPath(0, 270, R)} fill="none" stroke={G.line} strokeWidth={10} strokeLinecap="round" />
<path d={arcPath(225, 270, R)} fill="none" stroke={RED} strokeWidth={10} strokeLinecap="round" opacity={0.85} />
{ticks}
{/* 指针:琥珀刚体,rotate 全量程往返 */}
<g transform={`rotate(${d.toFixed(3)} ${CX} ${CY})`}>
<line x1={tailX} y1={tailY} x2={tipX} y2={tipY} stroke={AMBER} strokeWidth={9} strokeLinecap="round" />
</g>
<circle cx={CX} cy={CY} r={15} fill={G.ink} />
<circle cx={CX} cy={CY} r={6} fill={AMBER} />
</svg>
{/* 落定同帧弹出的数值 */}
<div style={{
position: 'absolute', left: 0, right: 0, top: 396, textAlign: 'center',
opacity: popOp, transform: `scale(${popScale.toFixed(4)})`,
}}>
<span style={{ fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 800, fontSize: 62, color: G.ink }}>
{value}
</span>
<span style={{ fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 700, fontSize: 30, color: G.mid, marginLeft: 8 }}>
%
</span>
</div>
</div>
);
};
export const NeedleSweepSelftest: React.FC = () => {
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="NEEDLE SWEEP SELF-TEST" 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', boxShadow: '0 2px 8px rgba(0,0,0,0.06)', padding: 32,
}}>
<div style={{ height: 16, width: 320, background: G.bar, borderRadius: 8 }} />
<div style={{ position: 'absolute', left: 0, top: 96, display: 'flex' }}>
{GAUGES.map((g, i) => (
<Gauge key={i} start={g.start} target={g.target} />
))}
</div>
</div>
</div>
);
};
// ===== demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx =====
// tape-scroll-fixed-pointer —— 滚带定针
// 取景窗+琥珀三角指针钉死在画面正中,竖向长刻度带(0–500,每 50 一大格)
// 从窗后滚过:"世界动、针不动"。先慢速爬(f12–55 读数 60→140 缓涨),
// 然后一次大跳变:刻度带冲刺 ~23f(峰值 ~50px/f)冲过 420 到 442,
// spring 刹车回摆一次(442→415→420)落定。窗内读数同步。f96 后真静止 44f。
// 帧确定性:value(frame) 纯分段 interpolate 全 clamp,带偏移 = value 线性映射。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, TitleBlock } from '../../_fixtures/Fixtures';
const AMBER = '#b45309';
const PXU = 3; // px per unit:500 量程 → 1500px 长带
const CENTER_Y = 590; // 取景窗中线(屏幕坐标)
const TAPE_X = 830; // 刻度带左缘
const TAPE_W = 260;
// 读数时间线:静置→慢爬→冲刺(过冲到442)→回摆(415)→落定420→真静止
const valueAt = (frame: number): number => {
if (frame <= 12) return 60;
if (frame <= 55) {
return interpolate(frame, [12, 55], [60, 140]); // 慢爬 ~5.6px/f
}
if (frame <= 78) {
return interpolate(frame, [55, 78], [140, 442], {
easing: Easing.inOut(Easing.cubic), // 冲刺段,峰值 ~50px/f
});
}
if (frame <= 88) {
return interpolate(frame, [78, 88], [442, 415], {
easing: Easing.out(Easing.cubic), // 刹车回摆:甩过头再拉回
});
}
return interpolate(frame, [88, 96], [415, 420], {
easing: Easing.out(Easing.cubic),
extrapolateRight: 'clamp',
});
};
export const TapeScrollFixedPointer: React.FC = () => {
const frame = useCurrentFrame();
const v = valueAt(frame);
// 刻度带:数值大在上。单位 u 的屏幕 y = CENTER_Y + (v - u) * PXU
const yOf = (u: number): number => CENTER_Y + (v - u) * PXU;
const ticks: React.ReactNode[] = [];
for (let u = 0; u <= 500; u += 10) {
const y = yOf(u);
if (y < 180 || y > 1010) continue; // 视野外裁剪
const major = u % 50 === 0;
ticks.push(
<div key={u} style={{
position: 'absolute', top: y - 2, right: 0,
width: major ? 92 : 46, height: major ? 5 : 3,
background: major ? G.ink : G.mid, borderRadius: 2,
}} />
);
if (major) {
ticks.push(
<div key={`n${u}`} style={{
position: 'absolute', top: y - 22, right: 110,
fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 700,
fontSize: 38, color: G.mid, textAlign: 'right', width: 90,
}}>
{u}
</div>
);
}
}
return (
<div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 90, width: '100%', textAlign: 'center' }}>
<TitleBlock text="TAPE SCROLL · FIXED POINTER" size={68} />
</div>
{/* 刻度带容器(世界层:整体在动) */}
<div style={{
position: 'absolute', left: TAPE_X, top: 180, width: TAPE_W, height: 830,
background: G.panel, border: `2px solid ${G.border}`, borderRadius: 12,
overflow: 'hidden', boxSizing: 'border-box',
}}>
<div style={{ position: 'absolute', inset: 0 }}>{
// 内层坐标 = 屏幕 y - 180
<div style={{ position: 'absolute', left: 0, right: 14, top: -180, height: 1080 + 400 }}>
{ticks}
</div>
}</div>
</div>
{/* 固定层:取景窗 + 琥珀三角指针 + 读数(针不动) */}
<div style={{
position: 'absolute', left: TAPE_X - 10, top: CENTER_Y - 46,
width: TAPE_W + 20, height: 92,
border: `5px solid ${AMBER}`, borderRadius: 10,
boxShadow: '0 4px 14px rgba(0,0,0,0.14)', boxSizing: 'border-box',
background: 'rgba(255,255,255,0.14)',
}} />
<svg width={60} height={64} style={{ position: 'absolute', left: TAPE_X - 66, top: CENTER_Y - 32 }}>
<polygon points="4,4 56,32 4,60" fill={AMBER} />
</svg>
{/* 同步读数窗 */}
<div style={{
position: 'absolute', left: TAPE_X + TAPE_W + 70, top: CENTER_Y - 84,
width: 380, height: 168, background: G.card,
border: `2px solid ${G.border}`, borderRadius: 14,
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
display: 'flex', flexDirection: 'column', justifyContent: 'center',
padding: '0 36px', boxSizing: 'border-box',
}}>
<div style={{ height: 12, width: 150, background: G.line, borderRadius: 6, marginBottom: 12 }} />
<div style={{ fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 800, fontSize: 96, color: AMBER, letterSpacing: -2, lineHeight: 1 }}>
{Math.round(v)}
</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/data/gauge-readout-moves/NeedleSweepSelftest.tsx, demos/data/gauge-readout-moves/TapeScrollFixedPointer.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.