Motion · Transição

Page-turn transitions

Two full-page transitions: cube-rotate turns two pages like adjacent cube faces, and barn-door-split splits the old page open like doors.

1920×108016:930 fps4.72s · 142 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

cube-rotate
两页贴立方体相邻面(rotateY 0/90° + translateZ W/2),场景层转 -90°;旧面转出压暗、新面转进变亮,45° 时两面夹一条暗棱
并列章节翻篇;产品两大功能区切换
barn-door-split
旧页两个 960px overflow 容器对位拼合,同时向外 Easing.in(cubic) 滑出画外;裂缝内边缘亮线+投影,新页底层 scale 1.06→1 迎上
新旧交替:改版前后、方案 A→方案 B

When to use

Chapter-level page changes: a "turning the page" ritual between two parallel major sections; division of labor with the shot-transitions family (camera hand-off) — that's "the camera moves across," this is "the page itself is a physical object"

Intention

The shot-transitions six and transition-travel/hidden-cut all treat the page as a "scene" — the camera moves between scenes. This card treats the page as a physical object: it has thickness, weight, it can turn over and split apart. The physical-object metaphor carries more ceremony, suited to major chapter-level page changes (feature A wraps up → feature B begins) rather than small shot-to-shot hand-offs. A, the cube, is "spatially parallel" — the two pages are two faces of a box, implying a peer relationship; B, the barn door, is "the old one steps aside" — the old page tears open from the center and slides away while the new page rises up from below, implying replacement. Choose by semantics: use A for parallel page turns, B for old-to-new swaps.

Duration & energy

  • Duration: Per style: establish 30f + transition 20–38f + settle ≥40f, about 4.4–4.7s
  • Energy: Medium-high

Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)

意图

shot-transitions 六式和 transition-travel/hidden-cut 都把页面当"场景" ——相机在场景间移动。本卡把页面当实体:它有厚度、有重量、会翻面 会裂开。体块隐喻的仪式感更强,适合章节级大换页(功能 A 讲完→功能 B) 而非镜头级小交棒。A 立方体是"空间上并列"——两页是盒子的两面, 暗示同级关系;B 对开门是"旧的让位"——旧页从正中撕开滑走、新页 从底下迎上来,暗示取代关系。按语义选:并列翻篇用 A,新旧交替用 B。

两式选型

| 式 | 做法 | 适用 | |----|------|------| | A cube-rotate | 两页贴立方体相邻面(rotateY 0/90° + translateZ W/2),场景层转 -90°;旧面转出压暗、新面转进变亮,45° 时两面夹一条暗棱 | 并列章节翻篇;产品两大功能区切换 | | B barn-door-split | 旧页两个 960px overflow 容器对位拼合,同时向外 Easing.in(cubic) 滑出画外;裂缝内边缘亮线+投影,新页底层 scale 1.06→1 迎上 | 新旧交替:改版前后、方案 A→方案 B |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 结构命门 | 场景层 translateZ(−W/2) rotateY(θ) 把正面拉回 z=0 | 少了这层 hold 段页面尺度不对(被 perspective 推远) | | A 转速 | θ 0→−90° / 38f inOut cubic;中段叠 blur 1.5px 落定摘 | <28f 读不出体块感;>50f 读作慢慢挪 | | A 亮度对切 | 旧面 1→0.55、新面 0.55→1 + 接缝竖向渐变阴影 sin(pπ) | 无明暗对切两面读作同一张图在转 | | B 预告 | 裂前中缝 2px 细线闪现两次(18–22/25–29f) | 直接裂开观众没看到裂点在哪 | | B 滑出 | 各 translateX ∓980px / 20f Easing.in(cubic) 加速 | ease-out 滑出是"被推走",ease-in 才是"让位" | | B 撕裂边 | 内边缘 3px G.ink 亮线 + 8px 投影 | 无边线两半读作普通左右擦除 | | 收尾 | 新页落定真静止 ≥40f | R1 |

已知坑

  • demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,

首次实战须以真实素材回验

  • 一缝一式规矩同 shot-transitions;体块转场比镜头交棒更重,

全片 ≤2 次,连续两缝都用体块读作 PPT 翻页

  • A 式官方 @remotion/transitions cube() 是付费件——本卡是 CSS 自实现,

backlog 已有"评估官方转场原语"待选项,若引入官方件可对照

  • B 式两半必须对位拼合无缝(右半内层 translateX(−960px))——

静止时看出接缝就穿帮;真实截图注意奇数宽度取整

  • 转场前后两页都要有 ≥30f 静止建立/收尾——两端都在动,体块感被

运动淹没

参考实现

demos/transition/page-turn-transitions/ (BarnDoorSplit.tsx / CubeRotate.tsx)

Reference implementation (Remotion)

  • demos/transition/page-turn-transitions/BarnDoorSplit.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/page-turn-transitions/BarnDoorSplit.tsx
  • demos/transition/page-turn-transitions/CubeRotate.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/page-turn-transitions/CubeRotate.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/page-turn-transitions.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/transition/page-turn-transitions/BarnDoorSplit.tsx, demos/transition/page-turn-transitions/CubeRotate.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("page-turn-transitions")

Prompt

Use the video-shotcraft shot recipe "Page-turn 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/page-turn-transitions.json
Raw TSX: https://gimgs.net/motion/page-turn-transitions/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/transition/page-turn-transitions/BarnDoorSplit.tsx, demos/transition/page-turn-transitions/CubeRotate.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).

JSON · Markdown · llms.txt

7,396 chars · Apache-2.0Abrir template bruto ↗
// ===== demos/transition/page-turn-transitions/BarnDoorSplit.tsx =====
// 对开门裂幕(barn-door-split-reveal)——剪映"开门式"转场。
// FakeDashboard A 从画面正中垂直裂成左右两半:两个 960×1080 overflow:hidden
// 容器各装一份完整 A(右半内层 translateX(-960) 对位拼合),同时向外加速滑
// 出画外,露出底层 FakeDashboard B 从 scale 1.06 轻推到 1.0 迎上来。
// 裂缝各自内边缘 2px G.ink 亮线 + 8px 投影强调"撕开"。
// 关键帧:0–30 静止展示 A(18–22 / 25–29 中缝细线两次闪现预告裂点)→
// 30–50 两半各 translateX ∓980(Easing.in cubic 加速滑出)→
// 30–55 底层 B scale 1.06→1.0(out cubic)→ 55–130 全静止(75f)。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, FakeDashboard, TitleBlock } from '../../_fixtures/Fixtures';

export const BarnDoorSplit: React.FC = () => {
  const frame = useCurrentFrame();

  // 两半外滑位移:30–50f,0 → 980px,加速离场
  const slide = interpolate(frame, [30, 50], [0, 980], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.in(Easing.cubic),
  });

  // 底层 B:30–55f 从 1.06 轻推到 1.0 迎上来
  const bScale = interpolate(frame, [30, 55], [1.06, 1.0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });

  // 裂前预告:中缝细线两次闪现(帧确定的开关,无随机)
  const crackFlash =
    (frame >= 18 && frame < 22) || (frame >= 25 && frame < 29);
  // 裂开后内边缘亮线 + 阴影常驻(随门一起滑出画外)
  const tornEdge = frame >= 30;

  const edgeLine = (side: 'left' | 'right'): React.CSSProperties => ({
    position: 'absolute',
    top: 0,
    [side]: 0,
    width: 3,
    height: 1080,
    background: G.ink,
    boxShadow:
      side === 'right'
        ? '-8px 0 14px rgba(0,0,0,0.4)'
        : '8px 0 14px rgba(0,0,0,0.4)',
  });

  return (
    <div
      style={{
        width: 1920,
        height: 1080,
        background: G.bg,
        position: 'relative',
        overflow: 'hidden',
      }}
    >
      {/* 底层 FakeDashboard B:scale 1.06 → 1.0 迎上来 */}
      <div
        style={{
          position: 'absolute',
          left: 0,
          top: 0,
          width: 1920,
          height: 1080,
          transform: `scale(${bScale})`,
          transformOrigin: '50% 50%',
        }}
      >
        <FakeDashboard variant="B" />
      </div>

      {/* 左门:960×1080 视口,装完整 A 的左半 */}
      <div
        style={{
          position: 'absolute',
          left: 0,
          top: 0,
          width: 960,
          height: 1080,
          overflow: 'hidden',
          transform: `translateX(${-slide}px)`,
        }}
      >
        <div style={{ position: 'absolute', left: 0, top: 0, width: 1920, height: 1080 }}>
          <FakeDashboard variant="A" />
        </div>
        {tornEdge && <div style={edgeLine('right')} />}
      </div>

      {/* 右门:960×1080 视口,内层 translateX(-960) 对位拼合 */}
      <div
        style={{
          position: 'absolute',
          left: 960,
          top: 0,
          width: 960,
          height: 1080,
          overflow: 'hidden',
          transform: `translateX(${slide}px)`,
        }}
      >
        <div
          style={{
            position: 'absolute',
            left: 0,
            top: 0,
            width: 1920,
            height: 1080,
            transform: 'translateX(-960px)',
          }}
        >
          <FakeDashboard variant="A" />
        </div>
        {tornEdge && <div style={edgeLine('left')} />}
      </div>

      {/* 裂点预告:中缝 2px 细线闪现两次 */}
      {crackFlash && (
        <div
          style={{
            position: 'absolute',
            left: 959,
            top: 0,
            width: 2,
            height: 1080,
            background: G.ink,
          }}
        />
      )}

      <div style={{ position: 'absolute', left: 120, top: 96 }}>
        <TitleBlock text="BARN DOOR SPLIT" size={54} />
      </div>
    </div>
  );
};


// ===== demos/transition/page-turn-transitions/CubeRotate.tsx =====
// 立方体空间翻转(cube-rotate)——cube transition 转场自实现。
// FakeDashboard A / B 各缩放 0.82 居中,当立方体相邻两面:perspective 1400px,
// 面宽 W=1920*0.82,两面各 rotateY(面角) translateZ(W/2),场景整体先
// translateZ(-W/2) 再 rotateY(θ)(把正面拉回 z=0,保证 hold 时精确 0.82 尺度)。
// 旧面 A 转出画面 brightness 1→0.55 压暗,新面 B 从 +90° 侧转入 0.55→1 亮起;
// 接缝两侧各一条竖向渐变阴影,随角度 sin(pπ) 加深再消失;转动中段叠 blur(1.5px),
// 落定摘掉(filter 条件挂载,保证收尾逐帧完全相同)。
// 关键帧:0–30 静止展示 A → 30–68 θ 0→-90°(inOut cubic)翻转 → 68–140 B 真静止 72f。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, FakeDashboard, TitleBlock } from '../../_fixtures/Fixtures';

const S = 0.82;
const W = 1920 * S; // 面宽 = 立方体棱长
const H = 1080 * S;

// 单个立方体面:W×H 视口内放缩放 0.82 的整幅 dashboard
const Face: React.FC<{
  variant: 'A' | 'B';
  rot: number; // 面自身的 rotateY(A=0,B=90)
  brightness: number;
  seam: number; // 接缝阴影不透明度 0–1
  seamSide: 'right' | 'left'; // 阴影贴在哪条竖边(即共享棱一侧)
}> = ({ variant, rot, brightness, seam, seamSide }) => (
  <div
    style={{
      position: 'absolute',
      width: W,
      height: H,
      overflow: 'hidden',
      backfaceVisibility: 'hidden',
      transform: `rotateY(${rot}deg) translateZ(${W / 2}px)`,
      filter: `brightness(${brightness})`,
      background: G.bg,
    }}
  >
    <div style={{ transform: `scale(${S})`, transformOrigin: '0 0' }}>
      <FakeDashboard variant={variant} />
    </div>
    {seam > 0.01 && (
      <div
        style={{
          position: 'absolute',
          top: 0,
          [seamSide]: 0,
          width: 150,
          height: H,
          opacity: seam,
          background:
            seamSide === 'right'
              ? 'linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.55))'
              : 'linear-gradient(to left, rgba(0,0,0,0), rgba(0,0,0,0.55))',
        }}
      />
    )}
  </div>
);

export const CubeRotate: React.FC = () => {
  const frame = useCurrentFrame();
  // 翻转进度:30–68f,inOut cubic;两端 clamp 保证 hold 段逐帧恒定
  const p = interpolate(frame, [30, 68], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.inOut(Easing.cubic),
  });
  const theta = -90 * p;
  const brightA = 1 - 0.45 * p; // 旧面转出压暗 1→0.55
  const brightB = 0.55 + 0.45 * p; // 新面转入亮起 0.55→1
  const mid = Math.sin(p * Math.PI); // 0→1→0,45° 时最深
  const seam = mid * 0.9;
  const blur = mid * 1.5;

  return (
    <div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
      {/* blur 外包一层:>0.02 才挂 filter,落定后收尾帧完全相同 */}
      <div
        style={{
          position: 'absolute',
          inset: 0,
          filter: blur > 0.02 ? `blur(${blur}px)` : undefined,
        }}
      >
        <div
          style={{
            position: 'absolute',
            left: (1920 - W) / 2,
            top: (1080 - H) / 2,
            width: W,
            height: H,
            perspective: 1400,
          }}
        >
          <div
            style={{
              position: 'absolute',
              inset: 0,
              transformStyle: 'preserve-3d',
              transform: `translateZ(${-W / 2}px) rotateY(${theta}deg)`,
            }}
          >
            {/* 面 A:正面出发,绕左而去;接缝棱在其右边 */}
            <Face variant="A" rot={0} brightness={brightA} seam={seam} seamSide="right" />
            {/* 面 B:从 +90° 侧面转进来;接缝棱在其左边 */}
            <Face variant="B" rot={90} brightness={brightB} seam={seam} seamSide="left" />
          </div>
        </div>
      </div>
      <div style={{ position: 'absolute', left: 120, top: 60 }}>
        <TitleBlock text="CUBE ROTATE" size={54} />
      </div>
    </div>
  );
};


  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/page-turn-transitions/BarnDoorSplit.tsx, demos/transition/page-turn-transitions/CubeRotate.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