Motion · Rhythm
Speed ramp and freeze-annotate
Two frame-remap pacing moves — speed-ramp (fast → 0.2x gaze → fast) and freeze-annotate (flowing → freeze with a circled callout → unfreeze).
Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player
Variants
Nonlinear frame-number remap speed change — fast sweep suddenly drops to 0.2x to linger on the highlight, then accelerates away
The card stream freezes mid-flow on the full frame, a circled callout pops in to call out the point, then it unfreezes and keeps flowing
When to use
For slowing/stopping on one highlight in a card stream or long pan; use freeze-annotate in tutorial/explainer contexts
Intention
A constant-speed stream reads as a slideshow (R2); an all-fast stream leaves viewers unable to catch the highlight (R3). Speed-ramp creates a "sprint–gaze–sprint" inside one motion; freeze-annotate goes further — it simply stops, marker-circles the highlight, then moves on. Both share the same technical root (frame-number remap) but differ in meaning: speed-ramp is "a second glance while passing by," freeze is "stopping class to underline the point."
Duration & energy
- Duration: Speed-ramp: 4–5s total (slow window ≥40f); freeze-annotate: 4–5s total (freeze hold ≥45f)
- Energy: Medium-high (the speed contrast itself is the energy beat)
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
匀速流读作 PPT(R2),全程快流观众抓不住重点(R3)。变速在一条 运动里制造"冲刺-凝视-冲刺";定格标注更进一步——干脆停下来, 马克笔圈出重点再走。两款同一技术根(帧号 remap),语义不同: 变速是"路过时多看一眼",定格是"停课划重点"。
动效核心
- 源动画匀速,输出帧对源帧做非线性 remap:
src = interpolate(frame, [0,40,85,135], [0,88,97,207])(斜率即速率)
- 变速款:快段斜率 ≥2、慢窗斜率 ≈0.2(≥10 倍反差才可感);
blur 联动速率开关——快段包 CameraMotionBlur、慢窗裸渲, "快糊-慢清"反差是手法成立的一半
- 定格款:定格段斜率 =0(瞬时切换,无缓入——定格要干脆);
定格期强调色马克笔 SVG 椭圆 8f 描边(stroke-dashoffset)圈住目标 + 箭头 6f 点题,feTurbulence(scale≈7)给手绘抖动;解冻段斜率 >1 补偿时长,圈注 8f 淡出
- 目标卡用高清纹理并对位屏中(凝视/圈注对象必须锐利、居中)
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 慢窗/定格时长 | ≥40f / ≥45f | 短了凝视不成立(R3 宁慢勿快) | | 快慢斜率反差 | ≥10 倍 | 反差不足读不出"慢下来了" | | 圈注描边 | 8f 划完 + 抖动 scale 7 | 太慢像加载动画;无抖动像机械标注 |
已知坑
- 变速段 SFX 钉帧跟输出帧不跟源帧(画面动作在输出时间轴上发生);
拟音槽位:定格款配马克笔沙沙声(S4),本卡转正时未配、实战补
- 定格标注一支 30s 片 ≤2 次(同 D 式克制原则)
- 参数经占位素材调校转正,非实战定稿,首次实战后回验
参考实现
demos/rhythm/speed-ramp-freeze/ (FreezeAnnotateReal.tsx / SpeedRampReal.tsx)
Reference implementation (Remotion)
- demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx
- demos/rhythm/speed-ramp-freeze/SpeedRampReal.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/speed-ramp-freeze/SpeedRampReal.tsx
The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.
Everything on this page is also machine-readable. Point your agent at the JSON, or add the gimgs MCP server and ask it to search motion templates.
- Fetch the JSON — it holds the shot recipe (timing, easing, parameter table, pitfalls) and the full TSX source.
- Clone the video-shotcraft repo; the component imports shared fixtures from demos/_fixtures and helpers from assets/lib.
- Register the component as a <Composition> in a Remotion project and render with `npx remotion render`, or re-implement the recipe in your own engine.
curl -s https://gimgs.net/motion/speed-ramp-freeze.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx, demos/rhythm/speed-ramp-freeze/SpeedRampReal.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("speed-ramp-freeze")Prompt
Use the video-shotcraft shot recipe "Speed ramp and freeze-annotate" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/speed-ramp-freeze.json Raw TSX: https://gimgs.net/motion/speed-ramp-freeze/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx, demos/rhythm/speed-ramp-freeze/SpeedRampReal.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 4.54s 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/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx =====
// freeze-annotate 定格标注(轮 G)——真实卡片流运动中定格,
// 马克笔琥珀圈注(feTurbulence 手绘抖动)圈住目标卡 + 箭头点题,解冻继续。
// remap:0–45 流动 → 45–100 定格(斜率0)→ 100–135 解冻(1.4x 补偿)。
import { useId } from 'react';
import { AbsoluteFill, Img, interpolate, staticFile, useCurrentFrame } from 'remotion';
import layout from '../../_textures/live-layout.json';
export const FREEZEANNOTATE_DUR = 135;
const CARD_W = 460;
const GAP = 60;
const RAIL = layout.projects.cards.slice(0, 9);
const TARGET_I = 5;
const AMBER = '#b45309';
export const FreezeAnnotateReal: React.FC = () => {
const frame = useCurrentFrame();
// 滤镜 ID 按实例生成,多实例同场不串引(useId 的 «:» 在 url() 里非法,需清洗)
const roughId = `rough-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
const src = interpolate(frame, [0, 45, 100, 135], [0, 45, 45, 94], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
const dx = src * 22;
const draw = interpolate(frame, [52, 60], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
const arrowDraw = interpolate(frame, [60, 66], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
const fade = interpolate(frame, [96, 104], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
// 导轨起点 -880:定格时(dx=990)目标卡 k=5 中心 = -880+2600-990+230 = 960 屏中
const X0 = -880;
const targetX = X0 + TARGET_I * (CARD_W + GAP) - dx + CARD_W / 2;
const C = 1750; // 椭圆周长近似
return (
<AbsoluteFill style={{ backgroundColor: '#f9f6f1', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 370, transform: `translateX(${-dx + X0}px)` }}>
{Array.from({ length: 14 }).map((_, k) => {
const c = RAIL[k % RAIL.length];
const isTarget = k === TARGET_I;
return (
<Img
key={k}
src={staticFile(`textures/live/${isTarget ? 'card4-hires.png' : c.file}`)}
style={{
position: 'absolute', left: k * (CARD_W + GAP), top: 0,
width: CARD_W, borderRadius: 12,
boxShadow: '0 4px 16px rgba(31,28,23,0.10)',
}}
/>
);
})}
</div>
<svg width={1920} height={1080} style={{ position: 'absolute', inset: 0, pointerEvents: 'none', opacity: fade }}>
<defs>
<filter id={roughId}>
<feTurbulence type="fractalNoise" baseFrequency="0.02" numOctaves="2" seed="7" result="n" />
<feDisplacementMap in="SourceGraphic" in2="n" scale="7" />
</filter>
</defs>
<ellipse
cx={targetX} cy={560} rx={290} ry={230}
fill="none" stroke={AMBER} strokeWidth={8} strokeLinecap="round"
strokeDasharray={C} strokeDashoffset={C * (1 - draw)}
transform={`rotate(-6 ${targetX} 560)`}
filter={`url(#${roughId})`}
/>
<path
d={`M ${targetX + 330} 190 Q ${targetX + 220} 240 ${targetX + 140} 320`}
fill="none" stroke={AMBER} strokeWidth={8} strokeLinecap="round"
strokeDasharray={420} strokeDashoffset={420 * (1 - arrowDraw)}
filter={`url(#${roughId})`}
/>
</svg>
</AbsoluteFill>
);
};
// ===== demos/rhythm/speed-ramp-freeze/SpeedRampReal.tsx =====
// speed-ramp 变速(轮 C)——真实卡片流帧号 remap:快(斜率2.2) →
// 0.2x 慢速展示窗 → 快。慢速窗中目标卡(card4-hires)清晰滑过屏中。
// blur 联动速率:快段包 blur、慢段不包,反差即"凝视感"。
import { AbsoluteFill, Img, interpolate, staticFile, useCurrentFrame } from 'remotion';
import { CameraMotionBlur } from '@remotion/motion-blur';
import layout from '../../_textures/live-layout.json';
export const SPEEDRAMP_DUR = 135;
const CARD_W = 460;
const GAP = 60;
const RAIL = layout.projects.cards.slice(0, 9);
const TARGET_I = 5;
const Scene: React.FC = () => {
const frame = useCurrentFrame();
// remap:0–40f 走 88 源帧(快),40–85f 走 9 源帧(0.2x),85–135f 走 110(快)
const src = interpolate(frame, [0, 40, 85, 135], [0, 88, 97, 207], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
const dx = src * 28;
return (
<AbsoluteFill style={{ backgroundColor: '#f9f6f1', overflow: 'hidden' }}>
{/* 偏移 717:让慢速窗中点(src≈92.4,dx≈2587)时目标卡 k=5 中心落屏中 */}
<div style={{ position: 'absolute', top: 360, transform: `translateX(${-dx + 717}px)` }}>
{Array.from({ length: 16 }).map((_, k) => {
const c = RAIL[k % RAIL.length];
const isTarget = k === TARGET_I;
return (
<Img
key={k}
src={staticFile(`textures/live/${isTarget ? 'card4-hires.png' : c.file}`)}
style={{
position: 'absolute', left: k * (CARD_W + GAP), top: isTarget ? -16 : 0,
width: CARD_W, borderRadius: 12,
boxShadow: isTarget
? '0 10px 32px rgba(31,28,23,0.18)'
: '0 4px 16px rgba(31,28,23,0.10)',
}}
/>
);
})}
</div>
</AbsoluteFill>
);
};
export const SpeedRampReal: React.FC = () => {
const frame = useCurrentFrame();
const fast = frame < 42 || frame > 83;
return fast ? (
<CameraMotionBlur shutterAngle={200} samples={20}>
<Scene />
</CameraMotionBlur>
) : (
<Scene />
);
};
- 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/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx, demos/rhythm/speed-ramp-freeze/SpeedRampReal.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 |
|---|---|---|---|
| — | |||
More in this category

Trailer grammar: three beats
Three trailer-grammar moves — trailer-bumper, a fast-cut hook up front; card-footage-cadence, text cards interleaved with dialogue footage; smash-cut, a slam into stillness.

Spectrum-morph underline
Spectrum UI morph — the title underline splits into vertical bars that pulse like a spectrum for two bars, then closes back into a straight line; music visualization living inside the UI.

Smear-frame afterimages
Afterimage smear — a card drags 4 crisp, countable clones while panning fast, merging into one on landing; an animated take on motion blur.

Sakuga timing shift
On-3s cuts to on-1s — an element moves in choppy 3-frame steps, then snaps to buttery frame-by-frame speed at the climax.