Motion · Transition
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.
Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player
Variants
Page B sits underneath a fan-shaped clip-path polygon; a hand sweeps clockwise from screen center at constant speed through 360°, revealing B as it passes, its scanning edge carrying multiple bright lines
Data/status refresh meaning; dashboard-type pages
12 vertical 160px strips with overflow hidden and an inner full page offset by a negative margin to align; inside each strip, A's left edge shrinks via scaleX(1-p) while B's right edge expands via scaleX(p), staggered in delay to form a wave, with a bright line sweeping along the seam
Page-forward/advancing meaning; pages with a horizontal reading flow
When to use
A universal transition where neither the old nor new page moves and a geometric boundary sweeps across to complete the handoff; works anywhere, with no need for a suitable element already in the composition
Intention
The transition library already has three families: travel-through (drilling in), hidden-cut (concealed), and solid-body (the page as a physical object). This card is the fourth family, geometric wipe — neither the old nor new page moves, a geometric boundary sweeps across to complete the handoff, and the wipe's shape carries the meaning: style A's circular sweep is "a dashboard refreshing its data"; style B's strip sweep is "venetian blinds flipping open to change the page." The difference from shot-transitions style F's element-mask wipe: F uses a real element inside the page as the mask (depends on the composition), this card is pure geometry — its universality is the whole point. Choose by meaning: A for data refresh, B for a horizontal page-forward feel.
Duration & energy
- Duration: Single style: pre-state ≥20f + wipe 32–60f + tail ≥40f, ~5s (150f)
- Energy: Medium
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
转场库已有三族:穿越系(钻进去)、藏切系(遮住切)、体块系(页面是 实体)。本卡是第四族几何擦除系——新旧页都不动,一条几何边界扫过 完成交接,擦除的形状即语义:A 圆扫是"仪表盘刷新了一屏数据",B 条扫 是"百叶窗逐叶翻面换页"。与 shot-transitions F 元素遮罩擦除的区别: F 用页面内真实元素当遮罩(依赖构图),本卡是纯几何形——通用性即定位。 按语义选:数据刷新用 A,横向推进翻页用 B。
两式选型
| 式 | 做法 | 适用 | |----|------|------| | A clock-wipe | B 页上层套扇形 clip-path polygon,指针从屏心 12 点顺时针匀速扫 360°,扫过处露 B;扫描沿带多层亮线 | 数据/状态刷新语义;仪表盘类页面 | | B blinds-slice | 12 根 160px 竖条 overflow hidden + 内层整页负 margin 对位;条内 A scaleX(1-p) 左缘收缩、B scaleX(p) 右缘展开,错峰 delay 成波,缝上亮线随波扫 | 翻页/推进语义;横向阅读动线页面 |
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 扇形 | 中心屏心(960,540)、半径 1400 盖满四角,固定 73 顶点(SEGS=72) | 顶点数恒定且够密(40+)防锯齿跳变——命门 | | A 扫速 | 30–90f 指针 0→360° 纯 linear,双向 clamp | 雷达要匀速;加缓动就不是雷达是钟摆 | | A 亮线 | 四层 SVG line:26px 白 0.35 柔光 + 13px 白 0.60 + 9px 黑 0.55 暗描边 + 4px 白核 | 白底判例:纯提亮不可见,必须加暗描边;首渲 3px 白线太弱,加码 1.5x 才过 | | B 波 | 20–52f:delay=列号×2f,每条 10f Easing.in(cubic) | 交接点恒为 x+160(1-p),数学上无露底 | | B 缝亮线 | 三层 SVG:16px 白 0.45 柔光 + 6px 黑 0.55 暗描边 + 3px 白核,进出各 2f 淡入淡出 | 同 A 白底判例 | | 摘罩 | A 96f / B 52f 起条件卸载全部擦除结构,B 页直出 | opacity 0 不算摘,残留 clip-path 毁真静止 | | 收尾 | A 54f / B 98f 真静止(≥40f) | R1 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 擦除边界必须带亮线/高光——无亮线的 wipe 读作 PPT 转场,命门;
亮线在浅色区靠黑描边、深色区靠白核,两侧都要可读
- A 与 circle-match-iris 的区别:iris 是从锚点炸开的圆(半径在长),
clock 是角度在扫(半径恒定)——别因为都是圆混用
- 几何擦除全片 ≤2 次,且 A/B 别同片连用(两次"边界扫过"读作模板感)
- 亮线淡出与摘罩必须同帧衔接(A 90–96f 淡出、96f 卸载),
差一帧就是残线穿帮
参考实现
demos/transition/wipe-transitions/ (BlindsSlice.tsx / ClockWipe.tsx)
Reference implementation (Remotion)
- demos/transition/wipe-transitions/BlindsSlice.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/wipe-transitions/BlindsSlice.tsx
- demos/transition/wipe-transitions/ClockWipe.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/wipe-transitions/ClockWipe.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/wipe-transitions.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/transition/wipe-transitions/BlindsSlice.tsx, demos/transition/wipe-transitions/ClockWipe.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("wipe-transitions")Prompt
Use the video-shotcraft shot recipe "Geometric wipe 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/wipe-transitions.json Raw TSX: https://gimgs.net/motion/wipe-transitions/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/transition/wipe-transitions/BlindsSlice.tsx, demos/transition/wipe-transitions/ClockWipe.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 5.06s 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/wipe-transitions/BlindsSlice.tsx =====
// blinds-slice|百叶窗切条错峰擦除
// FakeDashboard A → B。12 根 160px 竖条,从左到右 delay=列号×2f,
// 每条 10f 内完成翻换:条内 A scaleX 1→0(origin 左缘)与 B scaleX 0→1
// (origin 右缘)共用同一进度 p(Easing.in(cubic)),交接缝恒等于
// x+160(1-p),数学上无露底。缝上亮线(柔光+暗描边+白核)随波扫过。
// 波 20–52f;52f 起摘罩(整页 B 直出、条结构与亮线全部卸载),
// 52–150f 真静止 98f ≥ 40f。帧确定,无随机源。
import React from 'react';
import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion';
import { FakeDashboard } from '../../_fixtures/Fixtures';
const STRIPS = 12;
const W = 160; // 每条宽 12×160 = 1920
const WAVE_START = 20;
const STAGGER = 2; // 列号 × 2f
const FLIP = 10; // 每条 10f 完成翻换
const WAVE_END = WAVE_START + (STRIPS - 1) * STAGGER + FLIP; // 52
// 条内某页的切片:外层 160 宽裁剪,内层整页 1920 负 margin 对位
const Slice: React.FC<{ x: number; variant: 'A' | 'B' }> = ({ x, variant }) => (
<div style={{ width: 1920, height: 1080, marginLeft: -x }}>
<FakeDashboard variant={variant} />
</div>
);
export const BlindsSlice: React.FC = () => {
const frame = useCurrentFrame();
// 摘罩:波完成后条结构全部卸载,B 整页直出
if (frame >= WAVE_END) {
return (
<AbsoluteFill style={{ background: '#ececea' }}>
<FakeDashboard variant="B" />
</AbsoluteFill>
);
}
const seams: { x: number; opacity: number }[] = [];
const strips = Array.from({ length: STRIPS }).map((_, i) => {
const x = i * W;
const start = WAVE_START + i * STAGGER;
const end = start + FLIP;
// 未开始:纯 A 切片;已完成:纯 B 切片
if (frame < start) {
return (
<div key={i} style={{ position: 'absolute', left: x, top: 0, width: W, height: 1080, overflow: 'hidden' }}>
<Slice x={x} variant="A" />
</div>
);
}
if (frame >= end) {
return (
<div key={i} style={{ position: 'absolute', left: x, top: 0, width: W, height: 1080, overflow: 'hidden' }}>
<Slice x={x} variant="B" />
</div>
);
}
// 翻换中:A、B 共用同一进度 p——A 宽 160(1-p) 靠左,B 宽 160p 靠右,
// 交接点恒为 x+160(1-p),无露底
const p = interpolate(frame, [start, end], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.in(Easing.cubic),
});
// 缝亮线:进出各 2f 线性淡入淡出
const seamOpacity = Math.min(
interpolate(frame, [start, start + 2], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }),
interpolate(frame, [end - 2, end], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }),
);
seams.push({ x: x + W * (1 - p), opacity: seamOpacity });
return (
<div key={i} style={{ position: 'absolute', left: x, top: 0, width: W, height: 1080, overflow: 'hidden' }}>
{/* A:向左缘收缩 */}
<div style={{ position: 'absolute', inset: 0, overflow: 'hidden', transform: `scaleX(${1 - p})`, transformOrigin: '0% 50%' }}>
<Slice x={x} variant="A" />
</div>
{/* B:从右缘展开 */}
<div style={{ position: 'absolute', inset: 0, overflow: 'hidden', transform: `scaleX(${p})`, transformOrigin: '100% 50%' }}>
<Slice x={x} variant="B" />
</div>
</div>
);
});
return (
<AbsoluteFill style={{ background: '#ececea' }}>
{strips}
{/* 缝亮线:白底判例——纯提亮不可见,柔光 + 暗描边 + 白核三层 */}
{seams.length > 0 && (
<svg width={1920} height={1080} style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
{seams.map((s, i) => (
<g key={i} opacity={s.opacity}>
<line x1={s.x} y1={0} x2={s.x} y2={1080} stroke="rgba(255,255,255,0.45)" strokeWidth={16} />
<line x1={s.x} y1={0} x2={s.x} y2={1080} stroke="rgba(0,0,0,0.55)" strokeWidth={6} />
<line x1={s.x} y1={0} x2={s.x} y2={1080} stroke="rgba(255,255,255,0.95)" strokeWidth={3} />
</g>
))}
</svg>
)}
</AbsoluteFill>
);
};
// ===== demos/transition/wipe-transitions/ClockWipe.tsx =====
// clock-wipe|时钟扫描擦除
// FakeDashboard A → B。30–90f 一根隐形雷达指针从 12 点方向顺时针扫一圈,
// B 页在上层用大扇形 clip-path polygon 逐帧张开;扫描沿带亮线(白核+暗描边+柔光)。
// 90–96f 亮线淡出,96f 起摘罩(B 直接满屏、无 clip-path、亮线卸载),
// 96–150f 真静止 54f ≥ 40f。帧确定,无随机。
import React from 'react';
import { AbsoluteFill, useCurrentFrame, interpolate } from 'remotion';
import { FakeDashboard } from '../../_fixtures/Fixtures';
const CX = 960;
const CY = 540;
const R = 1400; // 大于中心到角的距离 ~1101,扇形完全盖角
const SEGS = 72; // 顶点数固定且够密,避免锯齿跳变
// 12 点方向为 0°,顺时针(屏幕坐标 y 向下)
const polar = (deg: number, r: number): [number, number] => {
const a = (deg * Math.PI) / 180;
return [CX + r * Math.sin(a), CY - r * Math.cos(a)];
};
const fanClip = (theta: number): string => {
const pts: string[] = [`${CX}px ${CY}px`];
for (let i = 0; i <= SEGS; i++) {
const [x, y] = polar((theta * i) / SEGS, R);
pts.push(`${x.toFixed(1)}px ${y.toFixed(1)}px`);
}
return `polygon(${pts.join(', ')})`;
};
export const ClockWipe: React.FC = () => {
const frame = useCurrentFrame();
// 30–90f 指针 0→360°,linear(时钟扫描要匀速才像雷达)
const theta = interpolate(frame, [30, 90], [0, 360], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const wipeDone = frame >= 90;
// 亮线:扫描期间常亮,90–96f 线性淡出,96f 起条件卸载(摘罩判例)
const lineOpacity = interpolate(frame, [90, 96], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const lineMounted = frame >= 30 && frame < 96;
const [x2, y2] = polar(theta, R);
return (
<AbsoluteFill style={{ background: '#ececea' }}>
{/* 底层:A 页。擦完后卸载(上层 B 已满屏) */}
{!wipeDone && (
<AbsoluteFill>
<FakeDashboard variant="A" />
</AbsoluteFill>
)}
{/* 上层:B 页。扫描期挂扇形 clip-path,擦完后摘罩直出 */}
{frame >= 30 && (
<AbsoluteFill style={wipeDone ? undefined : { clipPath: fanClip(theta) }}>
<FakeDashboard variant="B" />
</AbsoluteFill>
)}
{/* 扫描亮线:柔光 + 暗描边 + 白核,从屏心指向当前角度 */}
{lineMounted && (
<svg
width={1920}
height={1080}
style={{ position: 'absolute', inset: 0, opacity: lineOpacity, pointerEvents: 'none' }}
>
{/* 柔光带(QA 后加码 1.5x:白底看不清就加深+加宽) */}
<line x1={CX} y1={CY} x2={x2} y2={y2} stroke="rgba(255,255,255,0.35)" strokeWidth={26} strokeLinecap="round" />
<line x1={CX} y1={CY} x2={x2} y2={y2} stroke="rgba(255,255,255,0.60)" strokeWidth={13} strokeLinecap="round" />
{/* 暗描边:白底上"提亮"不可见,加深保证浅色区也读得出指针 */}
<line x1={CX} y1={CY} x2={x2} y2={y2} stroke="rgba(0,0,0,0.55)" strokeWidth={9} strokeLinecap="round" />
{/* 白核 */}
<line x1={CX} y1={CY} x2={x2} y2={y2} stroke="rgba(255,255,255,0.95)" strokeWidth={4} strokeLinecap="round" />
</svg>
)}
</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/wipe-transitions/BlindsSlice.tsx, demos/transition/wipe-transitions/ClockWipe.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.

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.

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.

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.