Motion · Transition
Travel-through transitions
Two travel-through transitions — shared-element homecoming and letter-cavity pass-through; the camera drills through a real element inside the frame to complete the scene change.
Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player
Variants
A full-screen close-up card shrinks, shifts, and grows rounded corners, flying flush into its slot in the dashboard grid with a 3% overshoot settle
The "return to place" meaning of detail-to-overview; used paired with style F's window pass-through (going in the window / coming back to place)
A giant title's letter cavities (cut out with an SVG mask) reveal the new page through them; an exponential push drills through the cavity, and the instant it fills the frame the new page takes over, with the remaining strokes flung off-screen
A chapter title-to-body transition; a higher-energy alternative to style D's black-screen text card
When to use
Joins where the two shots share an element/container-level spatial relationship (a technique card, used complementarily with the six shot-transitions moves)
Intention
The six shot-transitions moves are "handoff" type — the outgoing shot closes, the incoming shot enters, and the join is covered by a white flash/black hold/focus pull/black screen/blur frame/window. This card's two styles are "travel-through" type — the background already exists as a real element inside the foreground frame (a grid slot / a letter cavity), and the camera makes one continuous spatial move drilling into it, so the viewer follows the same object the whole way with no handoff moment. The selection rule: if the background can already be "present in advance" inside the foreground, use travel-through; if the two shots are merely sequential with no spatial nesting, use handoff. The two styles further split by meaning — A is "putting this card back where it belongs" (detail-to-overview, the reverse of style F's window pass-through); C is "the title itself is the door" (a chapter text card and a transition rolled into one).
Duration & energy
- Duration: n/a (a technique card; each style's action segment is 25–60f, holds before/after counted separately, frames drawn from the neighboring shots' budget)
- Energy: n/a (a technique card, doesn't occupy an energy slot)
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
shot-transitions 六式是"交棒"型——前镜收尾、后镜入场,接缝靠白闪/暗场/ 焦点/黑场/糊帧/窗口盖住换页瞬间。本卡两式是"穿越"型——后景在前景画面里 以真实元素存在(网格槽位/字形洞),镜头一条连续空间运动钻进去, 观众从头到尾跟着同一个物体,没有交棒瞬间。选型口径:后景能在前景里"预先 在场"→穿越;两景只是先后关系、无空间嵌套→交棒。两式内部再按语义分—— A 是"把这张卡放回去"(详情→总览,F 式穿窗的逆向);C 是"标题即是门" (章节字卡与转场二合一)。
两式选型表
| 式 | 做法 | 适用接缝 | |----|------|----------| | A 共享元素归位 shared-element-morph | 全屏特写卡收缩+位移+长出圆角,严丝合缝飞落进 dashboard 网格所属槽位,3% 过冲落座 | 详情→总览的"归位"语义;与 F 式穿窗成对使用(进窗/回位)| | C 字腔穿越 letterform-zoom | 巨型标题字腔(SVG mask 挖洞)透出新页,指数推进穿洞,洞撑满瞬间接管、残余笔画甩出画外 | 章节标题→正文;替代 D 式黑场字卡的高能量款 |
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A morph 主曲线 | 25f bezier(0.4,0,0.2,1) 冲到 1.03 过冲 + 10f Easing.out(cubic) 弹回 1;位置/尺寸/圆角共用同一条进度 p | 三属性各走各的曲线立刻读作"变形"而非"同一物体";过冲 >5% 读作果冻 | | A 同一物体感 | 内容按槽位尺寸渲染、随容器等比缩放(scale 3.66→1,origin 左上);投影随 p 从 0/36/110/0.32 收敛到 0/2/8/0.06 | 内容重排版=换了张卡,穿越感断;投影不收敛则卡永远悬空、落不了座 | | A 落座仪式 | 背景 dashboard 0.9 透明度待命,落座瞬间 5f 提到 1 | 背景全程满亮则飞落没有"到家"的一拍;提亮窗口跨骑落座帧前后 | | C 穿腔主推 | 盖板 SVG mask(白底+黑字 900 字重)挖洞,transform-origin 对准腔心(demo:"DASH" A 腔 ≈773,508);scale=28^t,t 60f bezier(0.6,0,0.85,0.5) 慢起陡收 | ZOOM_MAX 按"洞撑满画面"配,字腔越小倍数越大;慢起段留给观众看清"洞里有东西" | | C 撤场与呼应 | scale∈[15,24] 盖板 opacity 1→0(残余笔画边飞边撤);期望模糊量 0→16px 除以 scale 写进 blur 补偿;洞内 B 层微 dolly 1→1.1、接管后 25f 落回 1 | blur 不除 scale 会被 transform 放大成糊墙;B 不跟着 dolly 则洞里是张死图,"穿进活页面"的感知断掉 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验;灰阶测不出纹理级缺陷(轮 A 判例),真实素材 上先出静帧自检
- C 式腔心坐标是字体度量估算(字宽 em 值手推),换字体/换词/渲染器字距
不同都会漂——渲后看第一帧推进方向,洞不居中就微调 origin,别信算出来的数
- A 式槽位坐标必须精确到像素(demo 里从 fixtures 布局逐项推算 808,590,
524x454,r14)——差几像素落座后再对齐,观众读作"飞到附近再吸附", "归位"的严丝合缝感全无
- 穿越两式与交棒六式同一接缝不叠加,一缝一式;穿越式已自带高能量,
再包白闪/震屏读作穿帮(规矩同 shot-transitions)
参考实现
demos/transition/transition-travel/ (LetterformZoom.tsx / SharedElementMorph.tsx)
Reference implementation (Remotion)
- demos/transition/transition-travel/LetterformZoom.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/transition-travel/LetterformZoom.tsx
- demos/transition/transition-travel/SharedElementMorph.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/transition-travel/SharedElementMorph.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/transition-travel.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/transition/transition-travel/LetterformZoom.tsx, demos/transition/transition-travel/SharedElementMorph.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("transition-travel")Prompt
Use the video-shotcraft shot recipe "Travel-through transitions" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/transition-travel.json Raw TSX: https://gimgs.net/motion/transition-travel/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/transition/transition-travel/LetterformZoom.tsx, demos/transition/transition-travel/SharedElementMorph.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/transition/transition-travel/LetterformZoom.tsx =====
import React, { useId } from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { FakeDashboard, G } from '../../_fixtures/Fixtures';
// letterform-zoom〔转场〕:巨型标题 "DASH" 字腔透出新页面,镜头急速推进
// 字母 A 的三角字腔,洞被撑满全屏的瞬间新页面接管,残余笔画滑出画外。
// 结构:底层 FakeDashboard B 静置全屏(微 dolly);上层"米灰盖板"用
// SVG <mask>(白底 + 黑字)在字形处挖洞——洞里即透出 B;对盖板整组做
// transform-origin 对准 A 字腔中心的指数 scale 1→28(60f,前 20f 慢
// 后 40f 陡)。scale 过临界值后盖板快速退场 = 撤掉 mask,B 全屏接管。
const FS = 560; // 标题字号
// A 字腔(三角洞)中心:Helvetica Bold 度量估算——
// "DASH" 总宽 ≈ 2.833em,居中起点 x≈167,A 占 571–975 → 腔心 x≈773;
// 基线 y=741(大写居中),腔心约在基线上方 0.42em → y≈508。
const ORIGIN = { x: 773, y: 508 };
const BASELINE = 741;
const ZOOM_MAX = 28;
const titleFont: React.CSSProperties = {
fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
fontWeight: 900,
fontSize: FS,
};
export const LetterformZoom: React.FC = () => {
const frame = useCurrentFrame();
// mask ID 按实例生成,多实例同场不串引(useId 的 «:» 在 url() 里非法,需清洗)
const cutId = `lfz-cut-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
// 0–25f 建立 hold;25–85f 推进。慢起 bezier 叠指数尺度 = 前段慢、后段陡
const t = interpolate(frame, [25, 85], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.bezier(0.6, 0, 0.85, 0.5),
});
const scale = Math.pow(ZOOM_MAX, t); // 指数推进:等比的"穿越"速度感
// scale 过临界值(洞已撑满画面中部)→ 盖板残余笔画边外飞边撤场(撤 mask)
const plateOpacity = interpolate(scale, [15, 24], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 速度模糊:期望视觉模糊量随推进升高;CSS filter 会被 transform 放大,
// 故除以 scale 补偿
const visBlur = interpolate(t, [0, 0.45, 1], [0, 1.5, 16], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const blurCss = visBlur / scale;
// 新页面微 dolly:推进期跟着轻推 1→1.1,接管后 25f 内落定回 1(收势)
const bScale =
frame < 85
? interpolate(t, [0, 1], [1, 1.1])
: interpolate(frame, [85, 110], [1.1, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.cubic),
});
return (
<AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
{/* 新页面:先只在字腔里透出,接管后全屏(110–140f 静止收尾) */}
<div
style={{
position: 'absolute',
inset: 0,
transform: `scale(${bScale})`,
transformOrigin: `${ORIGIN.x}px ${ORIGIN.y}px`,
}}
>
<FakeDashboard variant="B" />
</div>
{/* 米灰盖板(字形挖洞)+ 字缘描边 + 副标灰条:整组指数推进后飞出画外 */}
{plateOpacity > 0 && (
<div
style={{
position: 'absolute',
inset: 0,
transform: `scale(${scale})`,
transformOrigin: `${ORIGIN.x}px ${ORIGIN.y}px`,
opacity: plateOpacity,
filter: blurCss > 0.02 ? `blur(${blurCss}px)` : undefined,
}}
>
<svg
width={1920}
height={1080}
viewBox="0 0 1920 1080"
style={{ position: 'absolute', inset: 0, display: 'block' }}
>
<defs>
<mask id={cutId}>
<rect width={1920} height={1080} fill="#fff" />
<text
x={960}
y={BASELINE}
textAnchor="middle"
fill="#000"
style={titleFont}
>
DASH
</text>
</mask>
</defs>
<rect width={1920} height={1080} fill={G.bg} mask={`url(#${cutId})`} />
{/* 字缘细描边:让"洞"的轮廓在米灰上读得清 */}
<text
x={960}
y={BASELINE}
textAnchor="middle"
fill="none"
stroke={G.ink}
strokeWidth={3}
opacity={0.3}
style={titleFont}
>
DASH
</text>
</svg>
{/* 副标灰条:随盖板一起被甩出画外,强化"页面元素残余"感 */}
<div
style={{
position: 'absolute',
left: 0,
right: 0,
top: 812,
display: 'flex',
justifyContent: 'center',
gap: 16,
}}
>
<div style={{ width: 240, height: 16, background: G.bar, borderRadius: 8 }} />
<div style={{ width: 130, height: 16, background: G.line, borderRadius: 8 }} />
</div>
</div>
)}
</AbsoluteFill>
);
};
// ===== demos/transition/transition-travel/SharedElementMorph.tsx =====
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { FakeDashboard, Card, G } from '../../_fixtures/Fixtures';
// shared-element-morph〔转场〕:全屏特写面板收缩、位移、长出圆角,
// 严丝合缝飞落进 dashboard 网格里它所属的卡片槽位,落座带 3% 过冲。
// 观众感觉是同一个物体被镜头送回原位(FLIP 共享元素转场)。
//
// 目标槽位 = FakeDashboard A 中列第二行那格(seed 5),按 fixtures 布局精确推算:
// 侧栏 220 + 网格 padding 36 → 列宽 (1920-220-72-56)/3 = 524
// 头部 72 + padding 36 → 行高 (1008-72-28)/2 = 454
// 中列 x = 220+36+524+28 = 808;第二行 y = 72+36+454+28 = 590
const SLOT = { x: 808, y: 590, w: 524, h: 454, r: 14, seed: 5 };
const FULL = { x: 0, y: 0, w: 1920, h: 1080, r: 0 };
// 节拍:0–35 全屏特写 hold | 35–60 morph 25f(bezier 0.4,0,0.2,1 → 1.03)
// 60–70 过冲回弹落座 | 70–130 静止收尾
const MORPH_START = 35;
const MORPH_END = 60;
const SETTLE_END = 70;
const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
export const SharedElementMorph: React.FC = () => {
const frame = useCurrentFrame();
// 位置/尺寸/圆角共用同一条进度曲线:25f 冲到 1.03,再 10f 弹回 1
const drive = interpolate(frame, [MORPH_START, MORPH_END], [0, 1.03], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.bezier(0.4, 0, 0.2, 1),
});
const settle = interpolate(frame, [MORPH_END, SETTLE_END], [1.03, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.cubic),
});
const p = frame <= MORPH_END ? drive : settle;
const x = lerp(FULL.x, SLOT.x, p);
const y = lerp(FULL.y, SLOT.y, p);
const w = lerp(FULL.w, SLOT.w, p);
const h = lerp(FULL.h, SLOT.h, p);
const r = lerp(FULL.r, SLOT.r, p);
// 内容是同一张卡:按目标尺寸渲染,等比放大铺满全屏(全屏 = 这张卡的特写),
// 收缩过程中内容随容器一起缩小,强化"同一个物体"的感知
const contentScale = w / SLOT.w;
// 投影随尺寸缩小:悬空时大而深,落座后收敛到 fixtures 卡片原生投影量级
const ps = Math.min(Math.max(p, 0), 1);
const shadowY = lerp(36, 2, ps);
const shadowBlur = lerp(110, 8, ps);
const shadowAlpha = lerp(0.32, 0.06, ps);
// 背景 dashboard 先 0.9 透明度待命,落座瞬间提到 1
const bgOpacity = interpolate(frame, [MORPH_END - 2, MORPH_END + 3], [0.9, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.quad),
});
return (
<AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
<div style={{ opacity: bgOpacity }}>
<FakeDashboard variant="A" />
</div>
{/* 共享元素:全屏特写 → 精确飞落进槽位 */}
<div
style={{
position: 'absolute',
left: x,
top: y,
width: w,
height: h,
borderRadius: r,
overflow: 'hidden',
background: G.card,
boxShadow: `0 ${shadowY}px ${shadowBlur}px rgba(0,0,0,${shadowAlpha})`,
}}
>
<div
style={{
position: 'absolute',
left: 0,
top: 0,
transform: `scale(${contentScale})`,
transformOrigin: 'top left',
}}
>
<Card
w={SLOT.w}
h={SLOT.h}
seed={SLOT.seed}
style={{ boxShadow: 'none', borderRadius: r / Math.max(contentScale, 0.0001) }}
/>
</div>
</div>
</AbsoluteFill>
);
};
- 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/transition/transition-travel/LetterformZoom.tsx, demos/transition/transition-travel/SharedElementMorph.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

White flash: logo simplifies
A colorful liquid-gradient wordmark holds with flowing light, then the frame flashes white and overexposes; a flat wordmark fades in on white and settles — one flash completes the downgrade to simplicity.

Geometric wipe transitions
Two geometric wipe transitions — clock-wipe, a radar-hand sweep changing the page, and blinds-slice, 12 vertical strips flipping in staggered waves.

Hidden-cut transitions
Three hidden-cut transitions — foreground occlusion invisible cut, versus-slam opener, and warm light-leak burn; the hard cut hides inside 1-3 frames of occlusion/impact/light peak, so the viewer never sees the scissors.

Glitch-tear strip cut
A tearing transition — glitch-displace noise tear (16 horizontal strips jitter out of alignment, cut mid-jitter), a strip-level tear with digital-glitch meaning.