Motion · Typography
Outline word fill-in
A hollow-outline word snaps in from 3.2x scale, a dashed circle and guidelines close in, then the outline instantly fills solid white.
Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player
When to use
A hard hammer-beat for a single-word value prop/slogan; a "nail-down" shot on a rhythm cue point; an emphasis frame in dark-background brand films
Intention
Nailing down a word isn't done by fading it in slowly, it's done with two landings: first the giant hollow text snaps in fast from oversized scale (the size landing), then the outline turns solid white in a single frame (the material landing). The 1.5s in between, where the dashed circle contracts and the horizontal dashed guides extend inward, is an "aiming" process — technical-drawing vocabulary that makes the light-up read as "confirmation." The fill must be instantaneous — a slow sweep fill turns the nail into a slow decorative animation.
Duration & energy
- Duration: About 2.5s (75f@30fps, a short single beat)
- Energy: High (3.2x contraction + instant light-up, high impact in a short time)
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
一个词要打成钉子,靠的不是慢慢淡入而是两次落定:先是巨大空心字 急速收缩到位(这是尺寸的落定),再是描边在一帧内变实心白(这是质感的 落定)。中间那 1.5s 的虚线圆收缩与水平虚线内伸是"瞄准"过程——技术 制图语汇,让点亮那一下读作"确认"。填充必须是瞬时的:慢扫填充会把 钉子变成缓慢的美化动画。
动效核心
- 空心字急缓收缩:
zoom = seg(t, 0.05, 0.19, E.outCubic)(约 4f→14f,
仅 10f),scale(lerp(zoom, 3.2, 1));同期 born = seg(t, 0.05, 0.14) 淡入——收缩窗只 10f,快到几乎是一次冲击
- 双层字:
outline(color:transparent+-webkit-text-stroke:1px #565b63)
与 solid(纯白,初始 opacity 0)叠放,48px / 500 字重 (中等字重,不是标题的 800——空心字太粗会糊成块)
- 虚线大圆(SVG r=88、
dasharray:6 8):出现cin = seg(0.16, 0.3),
收缩 shrink = seg(0.16, 0.76, E.outCubic) 从 2.8 倍收到 1—— 收缩窗 45f,横跨大半片,是全片最慢的一条曲线;同时 rotate(t·18) 全程缓慢自转
- 左右水平虚线(
dasharray:5 7):ext = seg(0.5, 0.72, E.outCubic),x2 从
±240 向内推进 144(画框边缘 → 圆边),透明度同步等于 ext;随后 瞬时填充 pop = seg(t, 0.742, 0.762)——窗仅 0.02(约 0.6 帧), solid 与 outline 透明度硬交叉,视觉上是硬切
- 一闪即逝的辉光:
flash = pop · (1 - seg(0.762, 0.86, E.outQuad)),
0 0 flash·16px rgba(255,255,255,.45),约 7f 内衰完后定格纯白
- 描边预热:
bright = seg(0.66, 0.73, E.outQuad),灰度 86→145 —— 点亮前
先亮一档,给那 0.6 帧的硬切一个"充能"前奏;底层 14 颗静态微尘 (不动,随机位置 + 0.06–0.2 透明度)
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 收缩 | scale 3.2 → 1,窗 10f,outCubic | 3.2 倍是"从脸前退开"的量;<2 倍读作普通缩放;窗 >20f 就成了缓慢 zoom,冲击全失 | | 字重字号 | 48px / 500 / letter-spacing:.5px,占画宽约三成 | 空心字上 700+ 会让 1px 描边包住一大块空白,读作气球字;500 是空心字的甜点 | | 描边 | 1px #565b63,点亮前预热到灰度 145 | 1px 细描边是"制图"语汇;2px+ 变涂鸦;预热那一档删了硬切显得没由来 | | 虚线圆 | r=88、dash 6 8、scale 2.8→1(45f)、自转 t·18° | 45f 慢收缩是全片的"呼吸",与 10f 的字收缩形成快慢对比;窗压到 20f 两者撞在一起 | | 水平虚线 | dash 5 7,从 ±240 内伸 144,窗 0.5→0.72 | 内伸终点要停在圆边(不进圆内);伸满全宽读作十字准星,过头了 | | 瞬时填充 | seg(0.742, 0.762)(≈0.6f) | 这一下是全卡的目的:任何形式的慢扫(>3f)都会把钉子降级成美化动画 | | 辉光 | 峰值 16px,rgba(255,255,255,.45),7f 衰完 | 一闪即逝才像"点亮";持续辉光读作发光字体,质感立刻廉价 | | 微尘 | 14 颗,静态不动 | 静态是对的(本卡只 2.5s,动尘会抢戏);数量 >25 底噪太脏 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 只适合单个短词(demo
Faster,6 字母):字号与虚线圆半径是写死的
一组坐标,字一长就顶穿圆、水平虚线内伸终点也会压到字上——换成 两个词或中文四字须同步调 font-size / r / 内伸距离三处
-webkit-text-stroke是前缀属性,Chromium/WebKit 才生效;Firefox 下
outline 层是全透明(词直到 0.742 才突然出现)。Remotion 走 Chromium 无碍
- 瞬时填充窗
0.02在 75f 片长下不足一帧:这是故意的(要的就是硬切),
但如果把本卡拉长到 150f+,这 0.02 会变成 3 帧的可见渐变——拉长时长 必须把窗按比例压回去
- 时间窗注释里的秒数(4.83→5.15s 等)是原片时间轴的残留,与本卡
0–2.5s 的 t 无关,读代码时别被误导
- 深底专用:
#050505底 + 白描边 + 辉光是效果的全部;浅底上空心字
几乎不可见,需整套重配
- 声音强依赖:点亮那一帧必须有一记短促重音(金属/开关/低频撞击),
这是本卡"钉子"感的一半,配法纪律见 sound-design.md;与 word-relay-geometry 分工:那卡是三词接力(本卡是它第一段的独立化 + 升级版),同片二者不并置
参考实现
demos/typography/outline-word-fill/ (OutlineWordFill.tsx)
Reference implementation (Remotion)
- demos/typography/outline-word-fill/OutlineWordFill.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/outline-word-fill/OutlineWordFill.tsx
The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.
Everything on this page is also machine-readable. Point your agent at the JSON, or add the gimgs MCP server and ask it to search motion templates.
- Fetch the JSON — it holds the shot recipe (timing, easing, parameter table, pitfalls) and the full TSX source.
- Clone the video-shotcraft repo; the component imports shared fixtures from demos/_fixtures and helpers from assets/lib.
- Register the component as a <Composition> in a Remotion project and render with `npx remotion render`, or re-implement the recipe in your own engine.
curl -s https://gimgs.net/motion/outline-word-fill.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/typography/outline-word-fill/OutlineWordFill.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("outline-word-fill")Prompt
Use the video-shotcraft shot recipe "Outline word fill-in" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/outline-word-fill.json Raw TSX: https://gimgs.net/motion/outline-word-fill/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/typography/outline-word-fill/OutlineWordFill.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 3s 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).
// ===== demos/typography/outline-word-fill/OutlineWordFill.tsx =====
// outline-word-fill — Outline→Solid Fill 空心字辉光填充(motion-lab 定稿转原生 Remotion)
// 空心 "Faster"(细灰描边、中等字重)从约 3.2 倍巨大尺寸急缓收缩落位居中;
// 背后巨大虚线圆随后从画外持续收缩到字周围并缓慢自转,两侧水平虚线从画框边缘向内伸向圆;
// 描边先微微增亮,随即实心白色一帧内瞬间点亮(无慢扫),带一闪即逝的微辉光后定格纯白。
// 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。
import React from 'react';
import { DesignStage, E, lerp, rand, seg, useT } from '../../_fixtures/Motion';
export const OUTLINE_WORD_FILL_DURATION = 75; // 2500ms @30fps
// 背景微尘:位置/尺寸/透明度全部种子驱动,跨帧静止
const DUST = Array.from({ length: 14 }, (_, i) => ({
size: 1 + rand(i * 7 + 2) * 1.2,
opacity: 0.06 + rand(i + 55) * 0.14,
left: `${rand(i + 13) * 100}%`,
top: `${rand(i + 29) * 100}%`,
}));
export const OutlineWordFill: React.FC = () => {
const t = useT();
// 空心字从 ~3.2x 急缓收缩落位(原片 4.83→5.15s)
const born = seg(t, 0.05, 0.14, E.outCubic);
const zoom = seg(t, 0.05, 0.19, E.outCubic);
// 描边临近点亮前微微增亮(6.4→6.55s)
const bright = seg(t, 0.66, 0.73, E.outQuad);
const gv = Math.round(lerp(bright, 86, 145));
// 虚线圆:字落位后出现,从画外一路收缩到字周围(5.1→6.6s)
const cin = seg(t, 0.16, 0.3, E.outQuad);
const shrink = seg(t, 0.16, 0.76, E.outCubic);
// 水平虚线从画框边缘向内伸向圆(5.9→6.5s)
const ext = seg(t, 0.5, 0.72, E.outCubic);
// 实心白一帧内瞬间点亮(原片 6.54→6.56s 硬切,无慢扫)
const pop = seg(t, 0.742, 0.762);
// 一闪即逝的微辉光,随后定格纯白
const flash = pop * (1 - seg(t, 0.762, 0.86, E.outQuad));
// 注:原 effect.js 背景为 #050505,但样片 mp4 经 x264 编码后底色实测为 rgb(3,3,3)
// (暗部量化损失),此处按样片实际电平取 #030303 保证帧一致性。
return (
<DesignStage bg="#030303">
<div style={{ position: 'absolute', inset: 0, background: '#030303', overflow: 'hidden' }}>
{/* 背景微尘 */}
{DUST.map(({ size, opacity, left, top }, i) => (
<div
key={i}
style={{
position: 'absolute',
width: size,
height: size,
borderRadius: '50%',
background: '#fff',
opacity,
left,
top,
}}
/>
))}
{/* 虚线大圆(SVG,从画外收缩落位 + 缓慢自转)+ 左右水平虚线 */}
<svg
viewBox="-240 -135 480 270"
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}
>
<g transform={`scale(${lerp(shrink, 2.8, 1)}) rotate(${t * 18})`}>
<circle
cx={0}
cy={0}
r={88}
fill="none"
stroke="#7d838e"
strokeWidth={1}
strokeDasharray="6 8"
opacity={cin * 0.85}
/>
</g>
<line
y1={0}
y2={0}
x1={-240}
x2={-240 + ext * 144}
stroke="#7d838e"
strokeWidth={1}
strokeDasharray="5 7"
opacity={ext}
/>
<line
y1={0}
y2={0}
x1={240}
x2={240 - ext * 144}
stroke="#7d838e"
strokeWidth={1}
strokeDasharray="5 7"
opacity={ext}
/>
</svg>
{/* 双层文字(中等字重,占画宽约三成):底层描边、顶层实心白 */}
<div
style={{
position: 'absolute',
left: '50%',
top: '50%',
fontFamily: '-apple-system,"Helvetica Neue",sans-serif',
fontSize: 48,
fontWeight: 500,
letterSpacing: 0.5,
lineHeight: 1,
opacity: born,
transform: `translate(-50%,-53%) scale(${lerp(zoom, 3.2, 1)})`,
}}
>
<div
style={{
color: 'transparent',
WebkitTextStroke: `1px rgb(${gv - 4},${gv},${gv + 8})`,
opacity: 1 - pop,
}}
>
Faster
</div>
<div
style={{
position: 'absolute',
inset: 0,
color: '#fff',
opacity: pop,
textShadow:
flash > 0.01 ? `0 0 ${flash * 16}px rgba(255,255,255,${flash * 0.45})` : 'none',
}}
>
Faster
</div>
</div>
</div>
</DesignStage>
);
};
- Read the recipePrompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
- Get the codetool: git
git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install - Register + rendertool: remotion
npx remotion render <CompositionId> out.mp4 # component: demos/typography/outline-word-fill/OutlineWordFill.tsx - Or re-implementPort the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
| param | type | default | description |
|---|---|---|---|
| — | |||
More in this category

Word relay geometry
Three benefit words each carry their own dedicated geometry relay — a dashed circle spins and shrinks → three solid circles trim-grow in sequence → a metallic sheen sweeps then collapses to pure white; each new word outlines then fills in.

Word relay filmstrip
A left column of black-and-white page cards steps forward while a serif big word on the right relays in place (a fixed noun + a cycling verb) — the column steps only when the word changes, aligned to the current page card's midpoint.

Vertical word-roll cycle
The sentence's last word cycles on a vertical roller, 3 swaps at 0.55s each, fast then very slow with a slight overshoot; neighboring rows carry vertical blur and grayscale by distance, the centered word snapping from gray to accent color as it lands.

Two code reveals, side by side
The same syntax-highlighted code reveals two ways side by side — left: line-level stagger fading up 8px, right: character-by-character typing keeping each token's original color, with a #3a4468 block cursor on the current character.