Motion · Dados

Timeline travel pan

Timeline travel — the camera accelerates along a horizontal ruler axis past version tick marks, a card springs upright and pauses briefly at each tick, and an abrupt stop pushes in on the final tick.

1920×108016:930 fps5.38s · 161 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#data#camera

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

When to use

Changelog/milestone/company-history passages (another way to shoot "we've always been shipping"); complements scroll-brake-moves: that card is a vertical-list hard-brake, this card is a horizontal time journey

Intention

Shoot the product's version history as a journey along a timeline (the Hurt Locker title-sequence precedent): the camera eases in → sprints → hard-brakes in three speed phases moving horizontally, v1.0/v2.0/v3.0 ticks passing in sequence, and each passed tick springs a matching card upright — history flashing by outside the window, hard-stopping at "today" and pushing in. The speed itself says "development is fast," the hard-stop says "now matters most."

Duration & energy

  • Duration: 4–5s
  • Energy: Medium-high (an accelerate-then-hard-brake pacing shot)

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

意图

把产品的版本史拍成一次沿时间轴的旅行(《反恐王国》片头判例): 镜头缓起→冲刺→急刹三段变速横移,v1.0/v2.0/v3.0 刻度依次掠过, 每过一格对应卡片从刻度线上 spring 弹立——历史在窗外闪过, 急停在"今天"并推近。速度本身讲"发展快",急停讲"现在最重要"。

动作阶段

| 阶段 | 帧段参考 | 内容 | |------|------|------| | 1 静置 | f0–12 | 空轴 + 首刻度定住 | | 2 加速掠过 | ~f12–100 | 世界层 translateX 分段缓动:缓起→冲刺→末 12% 急刹 | | 3 弹卡 | 掠过中 | 相机扫过刻度前 6f 卡片起弹(spring damping ~11 明显过冲),底边为轴 scaleY 立起 | | 4 急停推近 | 急停帧起 10f | scale 1→1.28 推近末刻度(out cubic),接静止 ≥30f |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 变速曲线 | 输入断点 [0,0.15,0.88,1]→输出 [0,0.055,0.9,1] | 中段不够快就没有"旅行感";全程匀速即廉价 PPT | | 弹立时机 | 相机到达刻度前 6f 起弹,由 camXAt 反查 | 到了才弹观众已经错过;早太多读作预告 | | 刻度密度 | 主刻度 3–5 个 + 次刻度小点若干 | 次刻度点是速度感的参照物,删掉就感觉不到快 | | 急停推近 | 1→1.28 / 10f | 不推近急停就只是"停了",推近才是"到了" |

已知坑

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

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

  • 中段冲刺快到卡片糊时可叠 CameraMotionBlur(只包快速段,轮 #8 判例)
  • 急停后静止预算前置:demo 首版静止只有 25f 不达标,急停帧前移才够

——排时间线时先给末段留 ≥30f 再倒推行程

  • 每张弹立卡片是信息位:实战放真实版本亮点截图/一句话,

掠过速度要保证至少末两张能读(Q6 同源)

  • 声音:每次弹立一声轻打点,急停一声 impact(S2 同源)

参考实现

demos/data/timeline-travel/ (TimelineTravel.tsx)

Reference implementation (Remotion)

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

Prompt

Use the video-shotcraft shot recipe "Timeline travel pan" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/timeline-travel.json
Raw TSX: https://gimgs.net/motion/timeline-travel/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/data/timeline-travel/TimelineTravel.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 5.38s

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,422 chars · Apache-2.0Abrir template bruto ↗
// ===== demos/data/timeline-travel/TimelineTravel.tsx =====
// timeline-travel —— 时间轴横移(《反恐王国》式)
// 镜头沿水平刻度轴加速横移,v1.0/v2.0/v3.0/Today 四个刻度依次掠过,
// 每过刻度对应 Card 从刻度线 spring 过冲弹立 + 短停,镜头不停;
// 末刻度 4f 急停 + 推近 1.28×。世界层只动 translateX/scale。
// f0–12 初始静置;f118 起真静止 ≥42f(160f 总长)。
import React from 'react';
import { useCurrentFrame, interpolate, Easing, spring } from 'remotion';
import { G, Card, TitleBlock } from '../../_fixtures/Fixtures';

const W = 1920;
const AXIS_Y = 700;
const TICK_GAP = 1400; // 刻度间距(世界坐标)
const TICKS = [
  { label: 'v1.0', x: 960 },
  { label: 'v2.0', x: 960 + TICK_GAP },
  { label: 'v3.0', x: 960 + TICK_GAP * 2 },
  { label: 'Today', x: 960 + TICK_GAP * 3 },
];
const WORLD_W = 960 + TICK_GAP * 3 + 960;

const TRAVEL_START = 12;
const TRAVEL_END = 104; // 急停帧
const ZOOM_END = 114;

// 相机 X:in-out 但前段慢后段快(poly(3) in 为主,末端 out 急收)
// 用两段拼:0–0.82 加速段(Easing.in(poly(2.2))),0.82–1 急刹段
const camXAt = (f: number): number => {
  const total = TICKS[3].x - 960; // 需要位移的世界距离
  const t = interpolate(f, [TRAVEL_START, TRAVEL_END], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  // 加速→巡航→急刹:分段缓动,前 15% 缓起,中段近匀加速冲刺,末 12% 急收
  const eased = interpolate(t, [0, 0.15, 0.88, 1], [0, 0.055, 0.9, 1], {
    easing: Easing.inOut(Easing.quad),
  });
  return eased * total;
};

// 每张卡的弹立帧:相机中心扫过该刻度的时刻(数值上预先求好,避免逐帧求逆)
// 通过 camXAt 反查:找到 camX == tick.x - 960 的帧
const popFrameOf = (tickX: number): number => {
  for (let f = TRAVEL_START; f <= TRAVEL_END; f++) {
    if (camXAt(f) >= tickX - 960) return f;
  }
  return TRAVEL_END;
};

const CARD_W = 360;
const CARD_H = 240;

const TickStop: React.FC<{ i: number; frame: number }> = ({ i, frame }) => {
  const tick = TICKS[i];
  const pop = popFrameOf(tick.x) - 6; // 提前 6f 起弹,掠过时正好立起
  const s = spring({
    frame: frame - pop,
    fps: 30,
    config: { damping: 11, stiffness: 160, mass: 0.9 }, // 明显过冲
    durationInFrames: 26,
  });
  const appeared = frame >= pop;

  return (
    <div style={{ position: 'absolute', left: tick.x, top: 0 }}>
      {/* 刻度竖线 */}
      <div style={{ position: 'absolute', left: -3, top: AXIS_Y - 28, width: 6, height: 56, background: G.ink, borderRadius: 3 }} />
      {/* 刻度标签 */}
      <div style={{ position: 'absolute', left: -80, top: AXIS_Y + 44, width: 160, textAlign: 'center', fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 800, fontSize: 40, color: G.ink }}>
        {tick.label}
      </div>
      {/* 卡片从刻度线弹立:以底边为轴 scaleY 0→1(带过冲),伴随上移 */}
      {appeared && (
        <div
          style={{
            position: 'absolute',
            left: -CARD_W / 2,
            top: AXIS_Y - 36 - CARD_H,
            transform: `scaleY(${s}) scaleX(${0.6 + 0.4 * s})`,
            transformOrigin: '50% 100%',
            opacity: Math.min(1, s * 2),
          }}
        >
          <Card w={CARD_W} h={CARD_H} seed={i + 2} />
        </div>
      )}
    </div>
  );
};

export const TimelineTravel: React.FC = () => {
  const frame = useCurrentFrame();
  const camX = camXAt(frame);

  // 急停后推近末刻度:scale 1 → 1.28,中心对准 Today 刻度
  const zoom = interpolate(frame, [TRAVEL_END, ZOOM_END], [1, 1.28], {
    easing: Easing.out(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  return (
    <div style={{ width: W, height: 1080, background: G.bg, overflow: 'hidden', position: 'relative' }}>
      {/* 推近层:以画面中央偏下(末刻度落点)为原点放大 */}
      <div style={{ width: W, height: 1080, transform: `scale(${zoom})`, transformOrigin: '50% 62%' }}>
        {/* 世界层:唯一横移的容器 */}
        <div style={{ position: 'absolute', left: 0, top: 0, width: WORLD_W, height: 1080, transform: `translateX(${-camX}px)` }}>
          {/* 主轴线 */}
          <div style={{ position: 'absolute', left: 200, top: AXIS_Y - 3, width: WORLD_W - 400, height: 6, background: G.bar, borderRadius: 3 }} />
          {/* 次刻度(小点,增强速度感) */}
          {Array.from({ length: 22 }).map((_, i) => (
            <div key={i} style={{ position: 'absolute', left: 960 + i * (TICK_GAP / 5) - 2, top: AXIS_Y - 12, width: 4, height: 24, background: G.bar, borderRadius: 2 }} />
          ))}
          {TICKS.map((_, i) => (
            <TickStop key={i} i={i} frame={frame} />
          ))}
        </div>
      </div>
      <div style={{ position: 'absolute', top: 90, width: '100%', textAlign: 'center' }}>
        <TitleBlock text="TIMELINE TRAVEL" size={64} />
      </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/data/timeline-travel/TimelineTravel.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