Motion · Chữ & title card

Hai từ đối đầu rồi khớp lại

Hai từ đối đầu rồi khớp — trái "NEW" phải từ đặc tính đóng cứng ở lề màn hình đều nhau, cắt cứng luân phiên không co lại, tới từ cuối mới một lần duy nhất ease-in-out trượt vào giữa khớp thành cụm từ, chữ nhỏ bên dưới hiện gần như cắt cứng.

1920×108016:930 fps6.72s · 202 frameremotionGiấy phép: Apache-2.0#shotcraft#remotion#typography#outro

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

Đoạn thu gọn danh sách đặc tính về tên sản phẩm/khẩu hiệu (kiểu "NEW × chuỗi đặc tính → NEW <tên sản phẩm>"); recap kiểu sự kiện ra mắt, tiết lộ số phiên bản

Ý đồ

Hai từ đóng ở trái phải như hai trang mục lục đối đầu, khán giả tưởng chỉ là phụ đề luân phiên; đến khi từ cuối dừng lại mới phát hiện trái phải thực ra là hai nửa của cùng một câu — lần khớp liên tục duy nhất truy nhận cả đoạn luân phiên trước đó thành "dọn đường cho hồi hộp". Điểm mấu chốt là đóng cứng: suốt thời gian luân phiên vị trí hai từ không nhích một pixel, lề màn hình trái phải hoàn toàn bằng nhau (phim gốc đo thực tế 412 so với 413 @1280), bất kỳ co lại dần nào cũng sẽ lộ trước việc khớp lại, pha loãng màn tiết lộ thành thanh tiến trình. Khớp lại chỉ được xảy ra một lần, và phải sau từ cuối cùng — đây là tiết lộ kết đoạn, không phải hiệu ứng liên tục.

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

  • Thời lượng: Luân phiên 7–16f/từ × 8–9 từ + khớp lại khoảng 36f + chữ nhỏ đứng yên sau đó; toàn đoạn khoảng 5–6s
  • Năng lượng: Trung bình thấp (nhịp máy móc, chất bảng thông số chữ nhỏ, không phải đập chữ)

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)

意图

两词分踞左右像目录页对峙,观众以为只是轮换字幕;直到最后一词 停稳才发现左右其实是同一句话的两半——唯一一次连续合拢把整段 轮换追认为"悬念铺垫"。命门是钉死:轮换期两词位置一像素不动、 左右屏边距完全相等(原片实测 412 vs 413@1280),任何渐进收缩都会 提前剧透合拢、把揭晓稀释成进度条。合拢只许发生一次,且必须在 最后一词之后——这是收尾揭晓,不是持续动效。

动效核心

  • 左词左缘、右词右缘各自钉死在等屏边距处(原片实测 412/413px

@1280,×1.5 → 618/1302@1920);右词换长换短一律右缘定位

  • 词轮换纯硬切,停留帧数不均(16/12/9/8/7/8/10/12f,机器节奏

带人味);轮换全程间距零收缩(判例:渐进收缩版被用户驳回重做)

  • 唯一一次合拢:末词停稳 ~10f 后,~1.2s(36f)ease-in-out

(cubic)连续滑动,两词滑到以屏幕中线居中定格

  • 合拢终点按等宽字体实际步进算(0.6em+letterSpacing × 字符数),

两词恰好一空格咬合,无重叠无缝隙;无 stamp 缩放、无二次补偿移动

  • 定格 ~18f 后斜体小字近乎硬切浮现(4f 快淡、零位移),与整行

同左缘、字号同正文

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 屏边距 | 618px@1920(原片实测 412/413@1280 ×1.5) | 左右差 >2px 读作没对齐;边距是本卡身份,别改成黄金分割 | | 词停留 | 7–16f 不均 | 均匀停留读作节拍器;<7f 读不完词 | | 合拢触发 | 末词停稳 +10f(原片 32.4→32.7s 实测) | 停稳期太短,观众没意识到"这词不换了"就开始滑 | | 合拢时长 | 36f ease-in-out cubic(原片实测 ~1.2s 连续滑动) | <24f 读作弹跳归位;>50f 读作拖 | | 咬合步进 | 0.6em+letterSpacing/字符,等宽字体 | 非等宽字体步进不可预测,会重叠或漏缝 | | 字号 | 42@1080p(原片实测字高 ~20/720) | 这段是小字规格清单气质,放大到 60+ 就成了砸字(去用 cel-flash-stomp) | | 小字浮现 | 定格 +18f,4f 快淡零位移(原片 34.4→34.6s 间直接出现) | 加滑入/长淡入就抢了合拢的戏 |

已知坑

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

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

  • 与 type-rhythm-sync(字属性节拍)/beat-cut-moves(硬切节拍)撞

领域:那两张是"轮换本身即内容";本卡命门是位置钉死 + 末次合拢 的收尾揭晓,轮换只是铺垫。若段落没有"合拢揭晓"就别用本卡

  • 合拢只许一次——每切一词收一档的渐进收缩版已被原片对照证伪并

删除,别复活

  • 必须等宽字体(demo 用 SF Mono);比例字体下咬合终点算不准
  • 与原片残余差距:原片词换瞬间带 1 帧亮度闪烁(打字机换行感),

demo 是纯硬切;实战可选加回但闪烁 >1f 会读作故障

参考实现

demos/typography/text-column-converge/ (TextColumnConverge.tsx) 原片出处:raycast-teams.mp4 28–36s

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

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

Prompt

Use the video-shotcraft shot recipe "Hai từ đối đầu rồi khớp lại" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/text-column-converge.json
Raw TSX: https://gimgs.net/motion/text-column-converge/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/typography/text-column-converge/TextColumnConverge.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 6.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

3,933 chars · Apache-2.0Mở template gốc ↗
// ===== demos/typography/text-column-converge/TextColumnConverge.tsx =====
// text-column-converge —— raycast-teams(实测素材 28–36s 段)重做版:
// 原片测量(1280 宽):NEW 左缘钉死 x=412,特性词右缘钉死 x=867,
// 两词到左右屏边距相等(412 vs 413),轮换期间间距完全不收缩;
// 词换到 RAYCAST 后才发生唯一一次合拢——约 1.2s ease-in-out 连续滑动
// (左缘 412→554 / 右缘 867→725),"NEW RAYCAST" 以屏幕中线居中定格;
// 定格后约 0.6s,斜体 "COMING 2026" 在下方近乎硬切浮现。
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';

// 词轮换表:停留帧数不均(机器节奏),全程钉在右缘,不做间距收缩
const STEPS: { word: string; dur: number }[] = [
  { word: 'LAUNCHER DESIGN', dur: 16 },
  { word: 'COMPACT MODE', dur: 12 },
  { word: 'HOTKEY RECORDER', dur: 9 },
  { word: 'HOTKEY TYPES', dur: 8 },
  { word: 'VOICE FEATURES', dur: 7 },
  { word: 'SETTINGS DESIGN', dur: 8 },
  { word: 'AI CHAT', dur: 10 },
  { word: 'FILE SEARCH', dur: 12 },
  { word: 'RAYCAST', dur: 999 }, // 最后一词:停稳后触发唯一一次合拢
];

const START = 8; // 开场黑场立静

// 原片 1280 宽 → 1920 宽换算(×1.5)
const NEW_LEFT_EDGE = 618; // 412×1.5:NEW 左缘(= 左屏边距)
const WORD_RIGHT_EDGE = 1302; // 868×1.5:特性词右缘(= 右屏边距,1920-1302=618 对称)

const FS = 42; // 原片字高很小(720p 下 cap ~20px → 1080p ~30px → 字号 ~42)
const LSP = 3; // letterSpacing
// 合拢终点按本字体实际步进计算(监视器等宽:0.6em + letterSpacing),
// 保证 "NEW RAYCAST" 恰好一个空格咬合、整行居中于 960,不会重叠
const ADV = 0.6 * FS + LSP; // 每字符步进
const LINE_W = 11 * ADV; // "NEW RAYCAST" 共 11 字符
const MERGED_LEFT = 960 - LINE_W / 2; // 合拢后 NEW 左缘
const MERGED_RIGHT = 960 + LINE_W / 2; // 合拢后 RAYCAST 右缘
const CONVERGE_DUR = 36; // 合拢时长:原片 ~1.2s ≈ 36 帧
const CONVERGE_DELAY = 10; // RAYCAST 停稳后先静置 10 帧再合拢(原片 32.4→32.7s)
const SUB_DELAY = 18; // 合拢定格后 ~0.6s 出斜体小字

export const TextColumnConverge: React.FC = () => {
  const f = useCurrentFrame();
  const t = f - START;

  // 定位当前步
  let acc = 0;
  let idx = 0;
  let stepStart = 0;
  for (let i = 0; i < STEPS.length; i++) {
    if (t >= acc) { idx = i; stepStart = acc; }
    acc += STEPS[i].dur;
  }
  const cur = STEPS[idx];
  const isLast = idx === STEPS.length - 1;
  const local = t - stepStart;

  // 唯一一次合拢:RAYCAST 停稳 CONVERGE_DELAY 帧后,ease-in-out 连续滑动
  const cvT = isLast ? local - CONVERGE_DELAY : -1;
  const cv = interpolate(cvT, [0, CONVERGE_DUR], [0, 1], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
    easing: Easing.inOut(Easing.cubic),
  });

  // NEW 左缘:618 → 831;特性词右缘:1302 → 1088
  const newLeft = interpolate(cv, [0, 1], [NEW_LEFT_EDGE, MERGED_LEFT]);
  const wordRight = interpolate(cv, [0, 1], [WORD_RIGHT_EDGE, MERGED_RIGHT]);

  const converged = cv >= 1;

  // 斜体小字:合拢定格后 SUB_DELAY 帧,近乎硬切(4 帧快速淡入,无位移)
  const subT = converged ? cvT - CONVERGE_DUR - SUB_DELAY : -1;
  const subOp = interpolate(subT, [0, 4], [0, 1], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
  });

  const visible = t >= 0;

  const font: React.CSSProperties = {
    fontFamily: '"SF Mono", Menlo, monospace',
    fontWeight: 500,
    fontSize: FS,
    letterSpacing: 3,
    color: '#f2f2f4',
    whiteSpace: 'nowrap',
    lineHeight: 1,
  };

  return (
    <AbsoluteFill style={{ background: '#050506', overflow: 'hidden' }}>
      {visible && (
        <div style={{ position: 'absolute', inset: 0 }}>
          {/* NEW:左缘定位(轮换期间钉死在左屏边距处) */}
          <div style={{
            ...font, position: 'absolute',
            left: newLeft, top: 519,
          }}>
            NEW
          </div>
          {/* 特性词:右缘定位(词换长换短,右缘不动) */}
          <div style={{
            ...font, position: 'absolute',
            right: 1920 - wordRight, top: 519,
          }}>
            {cur.word}
          </div>

          {/* 斜体小字:合拢后在整行正下方浮现,与整行同左缘 */}
          <div style={{
            ...font,
            fontStyle: 'italic',
            color: '#d8d8dc',
            position: 'absolute',
            left: MERGED_LEFT, top: 519 + FS + 14,
            opacity: subOp,
          }}>
            COMING 2026
          </div>
        </div>
      )}
    </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/typography/text-column-converge/TextColumnConverge.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