Motion · Transição

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.

1920×108016:930 fps5.06s · 152 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#transition

Fonte: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composição Remotion (React/TSX) — vídeo de prévia, sem player no navegador

Variantes

clock-wipe
B 页上层套扇形 clip-path polygon,指针从屏心 12 点顺时针匀速扫 360°,扫过处露 B;扫描沿带多层亮线
数据/状态刷新语义;仪表盘类页面
blinds-slice
12 根 160px 竖条 overflow hidden + 内层整页负 margin 对位;条内 A scaleX(1-p) 左缘收缩、B scaleX(p) 右缘展开,错峰 delay 成波,缝上亮线随波扫
翻页/推进语义;横向阅读动线页面

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.

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.

  1. Baixe o JSON — contém a receita do plano (timing, easing, tabela de parâmetros, armadilhas) e o código TSX completo.
  2. Clone o repositório video-shotcraft; o componente importa fixtures de demos/_fixtures e helpers de assets/lib.
  3. 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/wipe-transitions.json
git 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.mp4
claude 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).

JSON · Markdown · llms.txt

6,733 chars · Apache-2.0Abrir template bruto ↗
// ===== 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>
  );
};


  1. Read the recipe
    Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
  2. Get the code
    tool: git
    git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install
  3. Register + render
    tool: remotion
    npx remotion render <CompositionId> out.mp4 # component: demos/transition/wipe-transitions/BlindsSlice.tsx, demos/transition/wipe-transitions/ClockWipe.tsx
  4. Or re-implement
    Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
paramtypedefaultdescription
—

Mais nesta categoria