Motion · Chuyển cảnh

Thẻ lật lộ kết quả

Thẻ tính năng lật 180° quanh trục Y, mặt trước UI lóe một dải highlight di chuyển theo góc lật đúng lúc mép mỏng nhất, mặt sau lộ ra con số kết quả cỡ lớn, các thẻ lật so le lần lượt trên cả hàng.

1920×108016:930 fps4.89s · 147 frameremotionGiấy phép: Apache-2.0#shotcraft#remotion#transition#data

Nguồn: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composition Remotion (React/TSX) — có video preview, không có player trong trình duyệt

Khi nào dùng

Tường thuật cặp "tính năng → kết quả": một hàng thẻ tính năng lần lượt lật lộ chỉ số/kết luận riêng; chuyển cảnh cấp phần tử.

Ý đồ

Nhóm "lật" hiện có trong thư viện đã có wall-reveal-moves B (cả bức tường lật kiểu sóng, dùng để vào cảnh) và split-flap (lật ký tự, dùng cho chữ). Thẻ này là lật ngữ nghĩa: hai mặt của một thẻ tạo thành một cặp nhân quả — mặt trước là giao diện tính năng, mặt sau là con số kết quả nó mang lại. Bản thân động tác lật chính là câu trả lời cho "vậy thì sao?" — ngữ pháp chuẩn của đoạn bento-grid kiểu Apple. Lật so le 10f trên cả hàng ba thẻ đọc thành "kết quả báo cáo liên tiếp".

Thời lượng & năng lượng

  • Thời lượng: Một thẻ lật 26f, ba thẻ so le cách nhau 10f, toàn đoạn ~4.9s (kèm giữ hình).
  • Năng lượng: Trung bình cao

Công thức cảnh quay (bản gốc tiếng Trung — timing, easing, bảng tham số, lỗi hay gặp)

意图

库内"翻"字辈已有 wall-reveal-moves B(整墙波浪翻面,讲入场)和 split-flap(字符翻牌,讲文字)。本卡是语义翻面:一张卡的正反两面 是一对因果——正面是功能界面,背面是它带来的数字结论。翻面动作本身 就是"所以呢?"的回答,Apple bento 段落的标准语法。逐张错峰 10f 扫过 一排,三张卡三个数字节奏读作"成果连报"。

动效核心

  • perspective 1200px + transformStyle preserve-3d;双面各自

backfaceVisibility hidden,背面预转 rotateY(180°)

  • 翻转 18f Easing.bezier(0.55,0,0.3,1) 冲到 192°(12° 过冲,原案 8°

不可感已加码),再 8f Easing.out(poly(5)) 回落 180°

  • 侧棱高光:linear-gradient 位置随角度 35°→145° 从卡左外扫到右外,

强度 sin 包络在 90°(最薄瞬间)达峰 0.32 加深灰——白底用加深不用 提亮(判例);高光与角度联动才读作"光扫过转动的面"

  • 错峰 delay 10f/卡;全部翻完真静止 ≥40f

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 翻转时长 | 18f + 8f 回弹 | <14f 看不到侧棱瞬间;>28f 读作慢慢转 | | 过冲 | 192°→180°(12°) | 无过冲落定读作生硬停表 | | 高光峰值 | 0.32 加深灰 @90° | 峰值错开 90° 读作贴图错误 | | 错峰 | 10f/卡,≤4 卡 | 同时翻读作整墙翻面(那是 wall-reveal B 的活)| | 背面内容 | 大号数字(90px 级)+ 一行小标签 | 背面塞完整 UI 读不完——翻面后只有一拍注意力 | | 收尾 | 末卡落定真静止 ≥40f | R1 |

已知坑

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

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

  • 与 wall-reveal-moves B 式(网格波浪翻面)同技术根不同语义:那是

入场(背面灰卡→正面 UI),这是揭示(正面 UI→背面结论)——同片 两者都用时翻转方向要区分(入场 rotateX、揭示 rotateY)

  • 正反两面必须语义成对(界面→它的成果);翻出无关内容,翻面就

只是花哨的换图

  • 真实素材正面用截图时注意 backface 渲染开销,三卡以上分 Sequence
  • 声音候选位:每卡翻停帧一声轻"啪"(sound-design §4.5),三连啪

节奏感翻倍

参考实现

demos/transition/card-flip-reveal/ (CardFlipReveal.tsx)

Mã nguồn tham chiếu (Remotion)

  • demos/transition/card-flip-reveal/CardFlipReveal.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/card-flip-reveal/CardFlipReveal.tsx

Component import fixture chung từ demos/_fixtures và helper từ assets/lib của repo; xem SKILL.md trong repo để biết quy trình render.

Mọi thứ trên trang này đều có bản máy đọc. Đưa agent link JSON, hoặc thêm MCP server gimgs rồi bảo nó tìm motion template.

  1. Tải JSON — trong đó có công thức cảnh quay (timing, easing, bảng tham số, lỗi hay gặp) và toàn bộ source TSX.
  2. Clone repo video-shotcraft; component import fixture chung từ demos/_fixtures và helper từ assets/lib.
  3. Đăng ký component thành <Composition> trong project Remotion rồi render bằng `npx remotion render`, hoặc dựng lại công thức bằng engine của bạn.
curl -s https://gimgs.net/motion/card-flip-reveal.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/transition/card-flip-reveal/CardFlipReveal.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("card-flip-reveal")

Prompt

Use the video-shotcraft shot recipe "Thẻ lật lộ kết quả" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/card-flip-reveal.json
Raw TSX: https://gimgs.net/motion/card-flip-reveal/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/transition/card-flip-reveal/CardFlipReveal.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 4.89s

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

4,179 chars · Apache-2.0Mở template gốc ↗
// ===== demos/transition/card-flip-reveal/CardFlipReveal.tsx =====
// 功能卡 3D 翻面揭示(card-flip-reveal)——Apple bento 翻转段。
// 横排 3 张占位卡逐张错峰沿 Y 轴翻 180°(perspective 1200px,双面结构
// backface-visibility hidden),背面白卡中央大号结论数字。翻转先加速后
// 弹性落定(末端过冲 +12° 回 180°);翻到侧棱(90°)附近闪过一道随角度
// 移动的加深灰高光带(白底用加深而非提亮)。
// 关键帧(卡 i 起点 = 18 + i*10,i = 0/1/2):
//   0–18 hold → 卡0: 18–36 翻至 192° → 36–44 回弹落 180° →
//   卡1: 28–46–54,卡2: 38–56–64 → 64–145 三卡全静止(81f ≥ 40f)。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, Card, TitleBlock } from '../../_fixtures/Fixtures';

const CW = 440;
const CH = 300;
const GAP = 60;
const X0 = (1920 - (CW * 3 + GAP * 2)) / 2; // 240
const Y = (1080 - CH) / 2; // 390
const FLIP_START = 18;
const STAGGER = 10;
const FLIP_DUR = 18;
const SETTLE = 8;
const OVERSHOOT = 12; // 末端过冲角度(原案 8°,肉眼存疑加码到 12°)

const RESULTS = ['4.9×', '−38%', '99.9%'];

// 卡 i 在帧 f 的翻转角:0 → 192(先加速后减速)→ 180(弹性落定),帧确定
const angleAt = (f: number, i: number): number => {
  const s = FLIP_START + i * STAGGER;
  if (f < s + FLIP_DUR) {
    return interpolate(f, [s, s + FLIP_DUR], [0, 180 + OVERSHOOT], {
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp',
      easing: Easing.bezier(0.55, 0, 0.3, 1),
    });
  }
  return interpolate(f, [s + FLIP_DUR, s + FLIP_DUR + SETTLE], [180 + OVERSHOOT, 180], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.poly(5)),
  });
};

// 随角度移动的加深高光带:位置从卡左外扫到右外,强度在 90°(侧棱)达峰
const Sheen: React.FC<{ angle: number }> = ({ angle }) => {
  const pos = interpolate(angle, [35, 145], [-25, 115], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const op = Math.max(0, 1 - Math.abs(angle - 90) / 55);
  if (op <= 0.004) return null;
  return (
    <div
      style={{
        position: 'absolute',
        inset: 0,
        borderRadius: 14,
        pointerEvents: 'none',
        opacity: op,
        background: `linear-gradient(105deg, rgba(0,0,0,0) ${pos - 14}%, rgba(0,0,0,0.32) ${pos}%, rgba(0,0,0,0) ${pos + 14}%)`,
      }}
    />
  );
};

const FlipCard: React.FC<{ i: number; frame: number }> = ({ i, frame }) => {
  const angle = angleAt(frame, i);
  return (
    <div
      style={{
        position: 'absolute',
        left: X0 + i * (CW + GAP),
        top: Y,
        width: CW,
        height: CH,
        perspective: 1200,
      }}
    >
      <div
        style={{
          width: '100%',
          height: '100%',
          position: 'relative',
          transformStyle: 'preserve-3d',
          transform: `rotateY(${angle}deg)`,
        }}
      >
        {/* 正面:占位卡 */}
        <div style={{ position: 'absolute', inset: 0, backfaceVisibility: 'hidden' }}>
          <Card w={CW} h={CH} seed={i + 1} />
          <Sheen angle={angle} />
        </div>
        {/* 背面:白卡 + 大号结论数字(预先转 180°,翻满后正读) */}
        <div
          style={{
            position: 'absolute',
            inset: 0,
            backfaceVisibility: 'hidden',
            transform: 'rotateY(180deg)',
            background: G.card,
            border: `2px solid ${G.border}`,
            borderRadius: 14,
            boxSizing: 'border-box',
            boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          <span
            style={{
              fontFamily: 'Helvetica, Arial, sans-serif',
              fontWeight: 800,
              fontSize: 96,
              color: G.ink,
              letterSpacing: -2,
            }}
          >
            {RESULTS[i]}
          </span>
          <Sheen angle={angle} />
        </div>
      </div>
    </div>
  );
};

export const CardFlipReveal: React.FC = () => {
  const frame = useCurrentFrame();
  return (
    <div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
      <div style={{ position: 'absolute', left: 120, top: 96 }}>
        <TitleBlock text="CARD FLIP REVEAL" size={54} />
      </div>
      {[0, 1, 2].map((i) => (
        <FlipCard key={i} i={i} frame={frame} />
      ))}
    </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/card-flip-reveal/CardFlipReveal.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
—

Cùng danh mục