Motion · Luz & ênfase
Assemble Then Type Fly-In
On an empty dark grid, wordless UI skeletons fly in from all sides to lock together; then letters fly in via 3D rotation, headline first, until the page forms.
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
An opening where the page/poster "grows itself"; a capability showcase for layout/design-tool products; a two-stage narrative from skeleton to finished page.
Intention
Break "page generation" into two semantically distinct stages: first erect the skeleton (frames, cards, dividers, color blocks — all wordless), then fill in text. Text lands via letter-by-letter 3D rotation because the skeleton stage only uses flat translation — the two stages must differ in motion dimension, or viewers can't read "this is step two."
Duration & energy
- Duration: About 5.2s (156f@30fps).
- Energy: Medium-high (the skeleton phase is sparse, the text phase is dense; energy rises steadily toward the ending).
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
把"页面生成"拆成两个语义清楚的阶段:先立骨架(框、卡片、分隔线、 色块,全部无字),再填文字。文字用逐字 3D 旋转落位,是因为骨架段 只有平面位移——两段的运动维度必须不同,否则观众读不出"这是第二步"。
动效核心
- 6 个骨架件(顶栏 url pill、右侧短线、logo 点阵、模块卡、CTA pill、
社交方块)按 ft 0.04/0.07/0.10/0.13/0.17/0.20 依次飞入, seg(ft, ft+0.14, outBack) 过冲贴合,from 位移 60–220px + ±8° 旋转
- 骨架运动中
blur(≤2px)残影,a>0.97清零为none - 13 个文字块按
start0.34→0.76 依次开始:大标题最先(0.34/0.40),
然后 logo 字标、装饰大字,最后各处 MONO 小标注
- 逐字 3D:每个字符持有确定性种子随机的
dx ±170px / dy ±130px / dz −120→−420 / rotateX ±170° / rotateY ±190° / rotateZ ±120°,走 perspective(600px) translate3d + rotateX/Y/Z, seg(ft, ft+0.13, outCubic) 落位,a>=1 时 transform 直接设 none
- 块内字符间隔自适应:
step = clamp((0.94 − start − 0.13)/字符数, 0.002, 0.012)
——保证不论块多长都在 t≈0.95 前落完
- 随机全部来自
rand(i) = frac(sin(i*127.1 + 311.7) * 43758.5453),
确定性、可 seek
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 骨架时序 | 6 件,ft 0.04→0.20(间隔 0.03–0.04) | 骨架段占前 35%;压缩到 0.15 内就和文字段糊在一起 | | 骨架飞入 | outBack、0.14 时长、位移 60–220px + ±8° | 与 scanline-assemble-flyin 同一手法,去掉扫描线后靠时序读顺序 | | 骨架残影 | blur ≤2px,a>0.97 设 none | 阈值留 3% 裕量;直接判 a>=1 会因浮点残留一层糊 | | 文字起点 | 大标题 0.34/0.40,小标注 0.58→0.76 | 大字先落是可读性要求:小字先落观众会去读小字 | | 逐字位移 | dx ±170 / dy ±130 / dz −120→−420 | dz 是"从远处来"的主力;只给 dx/dy 就成了平面散入 | | 逐字旋转 | rotateX/Y/Z 各 ±120°–190° | 大角度才有"翻着飞"的手感;±30° 只像抖动 | | perspective | 600px(写在每个字符自己的 transform 里) | 值越小透视越夸张;1200px 以上 3D 感基本消失 | | 字间隔 | clamp((0.94−start−0.13)/n, 0.002, 0.012) | 自适应上限 0.012 是"逐字可辨"的下限速度 | | 落位清零 | a>=1 → transform:'none' | 不清零会留下 13 个块的合成层,长片会掉帧 |
已知坑
- 字符 span 走正常排版流(
display:inline-block),动画只动 transform——
不要用绝对定位逐字摆位,换文案就全乱
- 空格字符必须显式塞成
' '(代码里用ch === ' ' ? ' ' : ch),
否则 inline-block 的空 span 宽度归零,词间距消失
start是手写的 13 个数值,换文案(尤其字符数变化大时)要回看
最后一块是否仍在 t≈0.95 前落完;step 只保证块内,不保证块间
- 内容为中性占位模板("The headline for your product here" / "Acme Studio"
等),落地全部替换;A_RGB(159,182,232)为强调色槽位
- 与
scanline-assemble-flyin的区别是没有扫描线:那张靠扫描线
解释顺序,这张靠"先骨架后文字"的两段式解释顺序,两者不要混用
参考实现
demos/effects/assemble-then-type-flyin/ (AssembleThenTypeFlyin.tsx)
Reference implementation (Remotion)
- demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.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.
- Baixe o JSON — contém a receita do plano (timing, easing, tabela de parâmetros, armadilhas) e o código TSX completo.
- Clone o repositório video-shotcraft; o componente importa fixtures de demos/_fixtures e helpers de assets/lib.
- 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/assemble-then-type-flyin.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("assemble-then-type-flyin")Prompt
Use the video-shotcraft shot recipe "Assemble Then Type Fly-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/assemble-then-type-flyin.json Raw TSX: https://gimgs.net/motion/assemble-then-type-flyin/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 5.7s 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/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx =====
// assemble-then-type-flyin — Assemble + Type Fly-in 装配后文字 3D 落位(motion-lab 定稿转原生 Remotion)
// 空的暗底网格页面上,无文字的组件骨架(框、卡片、分隔线、色块)先从四面八方飞入贴合;
// 随后各处文字从 3D 空间逐字飞来——每个字符带独立的大角度 rotateX/Y/Z 旋转与纵深位移,
// 旋转着落到自己应在的位置,先大标题后小标注,全部落位后页面成形。
// 内容为中性占位模板,强调色可按项目替换。设计坐标 480×270(DesignStage 等比放大)。
import React from 'react';
import { DesignStage, E, lerp, rand, seg, useT } from '../../_fixtures/Motion';
export const ASSEMBLE_THEN_TYPE_FLYIN_DURATION = 156; // 5200ms @30fps
const MONO2 = "'SF Mono',Menlo,Consolas,monospace";
const SERIF2 = "Georgia,'Times New Roman',serif";
/* ---- 组件骨架(全部无文字):from=飞入起点位移,rot=起始旋转,ft=起飞时刻 ---- */
type Shell = { from: [number, number]; rot: number; ft: number };
const SH: Record<string, Shell> = {
urlPill: { from: [0, -60], rot: 0, ft: 0.04 },
topLine: { from: [80, -40], rot: 4, ft: 0.07 },
mark: { from: [-140, -30], rot: -8, ft: 0.1 },
card: { from: [220, 30], rot: 6, ft: 0.13 },
cta: { from: [-70, 120], rot: -4, ft: 0.17 },
social: { from: [130, 60], rot: 5, ft: 0.2 },
};
/* ---- 文字块(逐字 3D 飞入):段内 i 表示斜体;start=该块首字起飞时刻 ---- */
type TSeg = { s: string; i?: boolean };
type Block = { x: number; y: number; font: string; color: string; ls: number; start: number; segs: TSeg[]; dy?: number };
// 大字先落(0.34 起),小标注后落。dy 是基线补偿:29px Georgia 在 transform 缩放
// 渲染下的基线取整与原 DPR=4 截图差 0.5px,用 translateY 校回(top 会被整像素吸附)。
const BLOCKS: Block[] = [
{ x: 22, y: 64, font: `400 29px ${SERIF2}`, color: '#f2f3f6', ls: 0.3, start: 0.34, segs: [{ s: 'The headline for' }], dy: -0.5 },
{ x: 22, y: 100, font: `400 29px ${SERIF2}`, color: '#f2f3f6', ls: 0.3, start: 0.4, segs: [{ s: 'your product here', i: true }], dy: -0.5 },
{ x: 41, y: 35, font: `400 13px ${SERIF2}`, color: '#eceef2', ls: 0, start: 0.47, segs: [{ s: 'Acme ' }, { s: 'Studio', i: true }] },
{ x: 316, y: 96, font: `italic 400 36px ${SERIF2}`, color: '#f4f5f8', ls: 0, start: 0.5, segs: [{ s: 'sample', i: true }] },
{ x: 34, y: 172, font: `600 7.5px ${MONO2}`, color: '#e8e9ee', ls: 1.5, start: 0.58, segs: [{ s: 'GET STARTED' }] },
{ x: 122, y: 173, font: `500 7.5px ${MONO2}`, color: '#6a707c', ls: 1.5, start: 0.62, segs: [{ s: 'DOCS' }] },
{ x: 24, y: 14, font: `500 7px ${MONO2}`, color: '#8d93a0', ls: 1, start: 0.64, segs: [{ s: 'app.example.com' }], dy: 0.5 },
{ x: 306, y: 61, font: `500 6.5px ${MONO2}`, color: '#7c828e', ls: 1.5, start: 0.66, segs: [{ s: 'WORK' }] },
{ x: 432, y: 61, font: `500 6.5px ${MONO2}`, color: '#565b66', ls: 1.5, start: 0.68, segs: [{ s: '04 / 08' }] },
{ x: 306, y: 164, font: `500 6px ${MONO2}`, color: '#6a707c', ls: 1.5, start: 0.7, segs: [{ s: 'KINETIC TYPE · 04' }] },
{ x: 22, y: 143, font: `500 6.5px ${MONO2}`, color: '#565b66', ls: 1.5, start: 0.72, segs: [{ s: 'H1 · UI-SERIF / GEORGIA' }] },
{ x: 18, y: 246, font: `500 6.5px ${MONO2}`, color: '#4c515c', ls: 1.5, start: 0.74, segs: [{ s: 'A PRODUCT OF ACME · ACME LABS, INC.' }] },
{ x: 369, y: 242, font: `600 7.5px ${MONO2}`, color: '#c9cdd6', ls: 1.5, start: 0.76, segs: [{ s: '@USERNAME' }] },
];
// 逐字随机飞入参数:k 为全局字符序号(跨块累加,与 effect.js 的 charSeed 一致)
type CharP = { dx: number; dy: number; dz: number; rx: number; ry: number; rz: number };
let charSeed = 0;
const CHAR_PARAMS: CharP[][][] = BLOCKS.map((b) =>
b.segs.map((sg) =>
Array.from(sg.s, () => {
const k = charSeed++;
return {
dx: (rand(k) - 0.5) * 340,
dy: (rand(k + 50) - 0.5) * 260,
dz: -120 - rand(k + 99) * 300,
rx: (rand(k + 7) - 0.5) * 340,
ry: (rand(k + 13) - 0.5) * 380,
rz: (rand(k + 23) - 0.5) * 240,
};
})
)
);
export const AssembleThenTypeFlyin: React.FC = () => {
const t = useT();
// 阶段一:骨架四方飞入(无字)——outBack 回弹 + 运动残余 blur
const shell = ({ from, rot, ft }: Shell): React.CSSProperties => {
const a = seg(t, ft, ft + 0.14, E.outBack);
const sp = a > 0 && a < 0.97 ? 1 - a : 0;
return {
position: 'absolute',
opacity: t >= ft ? Math.min(1, seg(t, ft, ft + 0.05) * 1.5) : 0,
transform: `translate(${lerp(a, from[0], 0)}px,${lerp(a, from[1], 0)}px) rotate(${lerp(a, rot, 0)}deg)`,
filter: sp > 0.03 ? `blur(${sp * 2}px)` : 'none',
};
};
return (
<DesignStage bg="#0a0b0e">
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(180deg,#101116,#0c0d11)' }}>
{/* 底层网格 */}
<div
style={{
position: 'absolute',
inset: 0,
opacity: 0.5,
background:
'repeating-linear-gradient(0deg,transparent 0 23px,rgba(255,255,255,.025) 23px 24px),' +
'repeating-linear-gradient(90deg,transparent 0 23px,rgba(255,255,255,.025) 23px 24px)',
}}
/>
{/* 顶栏 url pill + 右侧短线 */}
<div style={{ ...shell(SH.urlPill), left: 18, top: 11, width: 78, height: 14, border: '1px solid #2a2c33', borderRadius: 9 }} />
<div style={{ ...shell(SH.topLine), right: 18, top: 16, width: 52, height: 5, background: '#1d1f26', borderRadius: 2 }} />
{/* logo 点阵 */}
<div style={{ ...shell(SH.mark), left: 24, top: 37, width: 10, height: 10 }}>
{[0, 1, 2, 3].map((i) => (
<div
key={i}
style={{
position: 'absolute',
width: 4,
height: 4,
borderRadius: '50%',
background: '#e8e9ee',
left: (i % 2) * 6,
top: (i >> 1) * 6,
}}
/>
))}
</div>
{/* 右侧模块卡(含分隔线与缩略块,无文字) */}
<div
style={{
...shell(SH.card),
left: 296,
top: 52,
width: 162,
height: 150,
background: '#121319',
border: '1px solid #23252d',
borderRadius: 5,
}}
>
<div style={{ position: 'absolute', left: 0, top: 24, width: '100%', height: 1, background: '#1e2028' }} />
<div style={{ position: 'absolute', left: 0, top: 104, width: '100%', height: 1, background: '#1e2028' }} />
{[0, 1, 2, 3].map((i) => (
<div
key={i}
style={{ position: 'absolute', left: 10 + i * 30, top: 124, width: 24, height: 16, background: '#1a1c23', borderRadius: 2 }}
/>
))}
</div>
{/* CTA pill 骨架 */}
<div style={{ ...shell(SH.cta), left: 22, top: 166, width: 74, height: 24, border: '1px solid #3a3d46', borderRadius: 12 }} />
{/* 社交 x 方块 */}
<div style={{ ...shell(SH.social), left: 352, top: 241, width: 11, height: 11, border: '1px solid #3a3d46', borderRadius: 2 }} />
{/* 阶段二:文字逐字 3D 旋转落位(间隔按块长自适应,保证 t≈0.95 前全部落位) */}
{BLOCKS.map((b, bi) => {
const n = CHAR_PARAMS[bi].reduce((acc, sgp) => acc + sgp.length, 0);
const step = Math.min(0.012, Math.max(0.002, (0.94 - b.start - 0.13) / n));
let ci = 0; // 块内字符序号(跨段累计,决定各字的错峰起飞)
return (
<div
key={bi}
style={{
position: 'absolute',
left: b.x,
top: b.y,
font: b.font,
color: b.color,
letterSpacing: b.ls,
whiteSpace: 'nowrap',
transform: b.dy ? `translateY(${b.dy}px)` : undefined,
}}
>
{b.segs.map((sg, si) => (
<span key={si} style={sg.i ? { fontStyle: 'italic' } : undefined}>
{Array.from(sg.s, (ch, k) => {
const c = CHAR_PARAMS[bi][si][k];
const ft = b.start + ci++ * step;
const a = seg(t, ft, ft + 0.13, E.outCubic);
return (
<span
key={k}
style={{
display: 'inline-block',
opacity: a > 0 ? Math.min(1, a * 1.8) : 0,
transform:
a >= 1
? 'none'
: `perspective(600px) translate3d(${lerp(a, c.dx, 0)}px,${lerp(a, c.dy, 0)}px,${lerp(a, c.dz, 0)}px) ` +
`rotateX(${lerp(a, c.rx, 0)}deg) rotateY(${lerp(a, c.ry, 0)}deg) rotateZ(${lerp(a, c.rz, 0)}deg)`,
}}
>
{ch === ' ' ? '\u00a0' : ch}
</span>
);
})}
</span>
))}
</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/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.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 |
|---|---|---|---|
| — | |||
Mais nesta categoria

Dark-stage spotlight reveals
Three dark-stage spotlight reveals — A wake-sleep sweep (light in = bright, light out = dark), B edge-hugging glow pan (violet light seeps along UI edges + steady rightward sweep), C corner reveal (radial light expands steadily from a corner to light the whole screen); light itself narrates in the dark.

Slam entrance moves
Three high-energy slam entrances: a whip-in perspective stop, a heavy KPI drop, and a shockwave that hits neighboring cards.

Scanline assemble fly-in
A scan line sweeps down an empty grid, flying each region's component in from off-screen — finishing as the scan ends.

Scanline annotate focus
A bright scan line sweeps down the page, framing each element it passes and labeling it with monospace annotations.