Motion · Tipografia

Text synced to audio

Two text-to-audio sync moves — font-weight-pump, strokes pumping thicker on the beat and springing back, and karaoke-fill-sync, words filling color one by one with the narration.

1920×108016:930 fps4.72s · 142 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#typography#rhythm

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

Variantes

font-weight-pumpfont-weight-pump 字重脉冲
命中帧笔画瞬间变粗(stroke+字重跳变),~10f 衰减弹回;重音拍额外撑宽 8%
音乐驱动的高能段;标题当低音炮
karaoke-fill-synckaraoke-fill-sync 填色随读
每词深色从左到右填亮,进度跟语速,读完保持;活跃词下带读指下划线
旁白/口播强调段;多词标语的视线引导

When to use

Title/tagline passages tightly bound to the audio track; A binds to the beat (drums), B binds to speech (narration, word by word)

Intention

The type-motion library so far is all about "how does it enter" — nothing covers "how does text that's already on screen live along with the sound." These two moves fill that gap: A turns the title into a rhythm organ — strokes pump thicker on the bass hit and spring back, the text dancing, good as a visual bass drop for high-energy passages; B turns a tagline into read-along captions — whichever word the narration reaches fills with color left to right, pulling the eye along, good for voiceover/spoken-emphasis passages. Their common trait: the letters don't move, their properties do — no displacement, no entrance/exit, purely weight or color breathing on the sound's beat.

Duration & energy

  • Duration: Single style 4–5s; A: each beat gets a 10f decay window; B: each word gets 15–35f depending on speech pace
  • Energy: A high (a dance-floor feel) / B medium (a follow-along guide)

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

意图

文字动效库内此前全是"入场怎么进",没有词管"在场的字怎么跟着声音活"。 这两式补上:A 让标题变成节奏器官——笔画随低音鼓抽动变粗又弹回, 文字在蹦迪,适合高能段落的视觉低音炮;B 让标语变成跟读字幕—— 旁白读到哪个词哪个词从左到右填色点亮,视线被牵着走,适合口播/ 标语强调段。共同点是字不动、属性动:无位移、无入退场, 纯粹是字重/颜色在声音的拍点上呼吸。

两式选型

| 式 | 做法 | 适用 | |----|------|------| | A font-weight-pump 字重脉冲 | 命中帧笔画瞬间变粗(stroke+字重跳变),~10f 衰减弹回;重音拍额外撑宽 8% | 音乐驱动的高能段;标题当低音炮 | | B karaoke-fill-sync 填色随读 | 每词深色从左到右填亮,进度跟语速,读完保持;活跃词下带读指下划线 | 旁白/口播强调段;多词标语的视线引导 |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 脉冲包络 | env=(1−t/10)^0.8(t=命中后帧数,≥10f 精确归 0);-webkit-text-stroke: 10px·env + fontWeight 在 env>0.15 窗口 400↔900 跳变 | stroke 连续衰减 + 字重离散跳变叠加才读作"连续变粗";单用 fontWeight 只有两挡、单用 stroke 变化太细 | | A 节拍 | 每 20f 一拍;重音拍额外 scaleX = 1+0.08·env(transform 缩放不改排版) | 拍距必须 > 衰减窗(20>10),两拍重叠读作抽搐 | | A 节拍参照物 | 画面下方一排小节拍点,命中哪拍哪个点闪深并放大 | 静音环境(预览/静帧)没有这排点就读不出"在跟拍" | | B 词级时间表 | 逐词 [start,end] 帧区间,词内 linear 填充;词间留 4–10f 停顿(换气) | 时间戳实战来自旁白打点表;均匀分配读作机器朗读 | | B 填色实现 | 每词双层同文本叠放:底层浅灰 + 上层深色 clipPath: inset(0 X% 0 0),X=(1−p)·100 | 逐词独立叠层免量测词宽占比;整行单层 clip 需精确像素表、易漂 | | B 读指 | 活跃词下 8px 深色下划线,宽度 = p·100% | 4px 浅灰在 1080p 缩略下不可见(本批实渲踩坑);读完的词不留线 | | 收尾 | A 末拍衰减归零后 / B 末词填完后真静止 ≥20f | R1 呼吸判例 |

已知坑

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

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

  • 两式都是声音强依赖手法:A 命中帧必须与鼓点同帧、B 填色区间必须

跟旁白词边界(钉帧走 sound-design §4.5 相对起点)——无声上片则 A 读作 抽风、B 读作莫名变色,别在无音轨段落用

  • A 式实战想要真·可变字体连续字重(wght 轴逐帧插值)需先确认字体加载

与渲染器支持;demo 的 stroke+跳变方案是无字体风险的兜底,效果已可感

  • A 式全片 ≤1 段(P4)——标题蹦迪是抢戏大户,两段同片互相拆台
  • B 式词数 ≤6/屏:更多词时填色进度读不过来,拆行或拆镜

参考实现

demos/typography/type-rhythm-sync/ (FontWeightPump.tsx / KaraokeFillSync.tsx)

Reference implementation (Remotion)

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

Prompt

Use the video-shotcraft shot recipe "Text synced to audio" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/type-rhythm-sync.json
Raw TSX: https://gimgs.net/motion/type-rhythm-sync/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/typography/type-rhythm-sync/FontWeightPump.tsx, demos/typography/type-rhythm-sync/KaraokeFillSync.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

6,051 chars · Apache-2.0Abrir template bruto ↗
// ===== demos/typography/type-rhythm-sync/FontWeightPump.tsx =====
// 字重脉冲(font-weight-pump)——标题笔画随节拍变粗又弹回,像文字跟着低音鼓蹦迪。
// 节拍每 20f 一拍:帧 30/50/70/90/110 命中。命中瞬间 -webkit-text-stroke 0→10px
// 跳满,随后 10f 幂衰减 (1-t/10)^0.8 弹回 0;fontWeight 在命中窗口 400→900 离散跳变
// (env>0.15),配合 stroke 连续衰减读作"连续变粗"。第 3、5 拍(帧 70/110)为重音,
// 额外 scaleX 1→1.08 同衰减(transform 缩放不改排版)。底部 5 个节拍点作节拍参照。
// 结构:0–29f 静止 hold;30–119f 五拍脉冲;120–139f 真静止收尾(20f,110+10=120 无残留)。
import React from 'react';
import { useCurrentFrame } from 'remotion';
import { G } from '../../_fixtures/Fixtures';

const BEATS = [30, 50, 70, 90, 110];
const ACCENTS = new Set([2, 4]); // 第 3、5 拍重音
const DECAY = 10; // 衰减帧数

// 命中后 10f 幂衰减包络:t=0 → 1,t>=10 → 精确 0(保证结尾真静止)
const envAt = (frame: number, beat: number) => {
  const t = frame - beat;
  if (t < 0 || t >= DECAY) return 0;
  return Math.pow(1 - t / DECAY, 0.8);
};

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

  // 只可能有一个活跃拍(拍距 20f > 衰减 10f),取最大包络及其拍序号
  let env = 0;
  let activeBeat = -1;
  BEATS.forEach((b, i) => {
    const e = envAt(frame, b);
    if (e > env) {
      env = e;
      activeBeat = i;
    }
  });

  const strokeW = 10 * env; // 笔画粗细连续衰减
  const weight = env > 0.15 ? 900 : 400; // 命中窗口离散跳字重
  const accent = activeBeat >= 0 && ACCENTS.has(activeBeat);
  const scaleX = accent ? 1 + 0.08 * env : 1; // 重音拍变宽一挡

  return (
    <div
      style={{
        width: 1920,
        height: 1080,
        background: G.bg,
        position: 'relative',
        overflow: 'hidden',
        fontFamily: 'Helvetica, Arial, sans-serif',
      }}
    >
      {/* 定宽居中容器,transform 缩放不改排版 */}
      <div
        style={{
          position: 'absolute',
          left: 0,
          top: 0,
          width: 1920,
          height: 1080,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        <div
          style={{
            fontSize: 150,
            fontWeight: weight,
            color: G.ink,
            letterSpacing: 6,
            whiteSpace: 'nowrap',
            WebkitTextStroke: `${strokeW}px ${G.ink}`,
            transform: `scaleX(${scaleX})`,
            transformOrigin: 'center center',
          }}
        >
          PUMP IT UP
        </div>
      </div>

      {/* 底部节拍点:5 个,命中哪拍哪个点闪 ink 并放大 */}
      <div
        style={{
          position: 'absolute',
          left: 0,
          bottom: 130,
          width: 1920,
          display: 'flex',
          justifyContent: 'center',
          gap: 56,
        }}
      >
        {BEATS.map((b, i) => {
          const e = envAt(frame, b);
          const dotOpacity = e > 0.02 ? Math.min(1, 0.3 + e * 1.2) : 0;
          const dotScale = 1 + 0.8 * e;
          return (
            <div
              key={i}
              style={{
                width: 26,
                height: 26,
                borderRadius: 13,
                background: G.line,
                position: 'relative',
                transform: `scale(${dotScale})`,
              }}
            >
              <div
                style={{
                  position: 'absolute',
                  inset: 0,
                  borderRadius: 13,
                  background: G.ink,
                  opacity: dotOpacity,
                }}
              />
            </div>
          );
        })}
      </div>
    </div>
  );
};


// ===== demos/typography/type-rhythm-sync/KaraokeFillSync.tsx =====
// 卡拉OK填色随读(karaoke-fill-sync)——旁白读到哪个词,哪个词就被深色从左到右
// 点亮。两行标语 "SHIP FASTER / BREAK NOTHING",每个词双层同文本叠放:底层 G.line
// 浅灰字,上层 G.ink 深字用 clip-path: inset(0 X% 0 0) 按词内进度线性填充(逐词独立
// 叠层,clip 百分比即词内进度,无需量测词在整行的像素占比)。词级时间表模拟语速:
// SHIP 20–38、FASTER 42–75(长词慢读)、BREAK 85–103、NOTHING 107–130,词间停顿。
// 正在填的词底下有 8px 深色下划线跟随填充右缘作读指。0–19f hold;130–149f 真静止。
import React from 'react';
import { useCurrentFrame, interpolate } from 'remotion';
import { G } from '../../_fixtures/Fixtures';

type Word = { text: string; start: number; end: number };

const LINES: Word[][] = [
  [
    { text: 'SHIP', start: 20, end: 38 },
    { text: 'FASTER', start: 42, end: 75 },
  ],
  [
    { text: 'BREAK', start: 85, end: 103 },
    { text: 'NOTHING', start: 107, end: 130 },
  ],
];

const KaraokeWord: React.FC<{ word: Word; frame: number }> = ({ word, frame }) => {
  // 词内 linear 填充进度,clamp 保证读完保持
  const p = interpolate(frame, [word.start, word.end], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const active = frame >= word.start && frame < word.end; // 正在读这个词
  return (
    <span style={{ position: 'relative', display: 'inline-block' }}>
      {/* 底层:浅灰未读字 */}
      <span style={{ color: G.line }}>{word.text}</span>
      {/* 上层:深字按进度从左到右揭开 */}
      <span
        style={{
          position: 'absolute',
          inset: 0,
          color: G.ink,
          clipPath: `inset(0 ${(1 - p) * 100}% 0 0)`,
        }}
      >
        {word.text}
      </span>
      {/* 读指下划线:只在正在填的词下出现,右缘跟随填充进度 */}
      {active && (
        <span
          style={{
            position: 'absolute',
            left: 0,
            bottom: -14,
            width: `${p * 100}%`,
            height: 8,
            background: G.ink,
          }}
        />
      )}
    </span>
  );
};

export const KaraokeFillSync: React.FC = () => {
  const frame = useCurrentFrame();
  return (
    <div
      style={{
        width: 1920,
        height: 1080,
        background: G.bg,
        overflow: 'hidden',
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        paddingLeft: 240,
        boxSizing: 'border-box',
        fontFamily: 'Helvetica, Arial, sans-serif',
        fontSize: 130,
        fontWeight: 800,
        letterSpacing: 2,
        lineHeight: 1.45,
      }}
    >
      {LINES.map((words, li) => (
        <div key={li} style={{ display: 'flex', gap: 48 }}>
          {words.map((w) => (
            <KaraokeWord key={w.text} word={w} frame={frame} />
          ))}
        </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/typography/type-rhythm-sync/FontWeightPump.tsx, demos/typography/type-rhythm-sync/KaraokeFillSync.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