Motion · Chữ & title card
Viên chip cuộn slot
Trong câu "Your `chip` Handled" nền trắng, từ trong viên nang đậm cuộn như slot máy xèng, viên nang co giãn khớp bề rộng chữ.
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
Câu bán hàng kiểu "chúng tôi đã lo xong ___"; câu kết liệt kê tính năng SaaS trong một dòng; nhịp hình ảnh chủ đạo cho phim thương hiệu nền sáng
Ý đồ
Xoay vòng từ trong ô có hai trường phái: né tránh thay đổi cho câu (cuộn bề rộng cố định), hoặc để câu chấp nhận thay đổi (viên nang giãn rộng, chữ hai bên bị đẩy giãn ra). Cảnh này thuộc trường phái sau, cái giá là phải tính bề rộng thật chuẩn, phần thưởng là câu đọc lên có sức sống — "Your" và "Handled" bị đẩy đi, người xem cảm nhận được trọng lượng của từ trong viên nang. Các mục bóng ma màu xám lộ ra phía trên/dưới viên nang là tín hiệu thứ hai: nó báo rõ "đây là một danh sách có thể cuộn", khiến người xem kỳ vọng còn từ tiếp theo.
Thời lượng & năng lượng
- Thời lượng: Khoảng 5.0s (150f@30fps: đứng yên → 3 lần đổi (0.25/0.47/0.69) → đứng yên đoạn cuối)
- Năng lượng: Trung bình (ba nhịp ổn định, co giãn bề rộng là chuyển động liên tục duy nhất)
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)
意图
词槽轮换有两派:让句子躲开变化(宽度固定的滚轮),或者让句子接受 变化(胶囊撑宽、两侧文字被挤开)。这卡是后者,代价是必须把宽度算准, 回报是句子读起来活的——"Your" 和 "Handled" 被推着走,观众感到胶囊里 的词有分量。上下露出的灰色幽灵项是第二个记号:它明示"这是个可以滚的 列表",让观众预期还有下一个词。
动效核心
- 宽度预量:setup 期用隐藏
<span>逐词量offsetWidth,各加 74px 内边距
存成 widths[](带 o.w.length·13 兜底,防 setup 时未挂载量不到)
- 位置量
pos由 3 段窗累加:切换点[0.25, 0.47, 0.69],每窗 0.12
(≈18f),缓动 E.inOutCubic——两头都缓,对应"滚轮起步与停稳"
- 内列
colIn竖移-pos·48px(行高 48 = 胶囊高);胶囊宽度取
lerp(frac, widths[ci], widths[ci+1]),帧内插值所以伸缩是连续的, 两侧 flex:none 的文字被 flex 布局自然挤开
- 幽灵项在胶囊外(chipWrap 内)上 -38px / 下 58px,取
near = round(pos)
的前后邻居文本,随滚动微移 (pos-near)·48·0.5(半速视差,比胶囊内 的词慢一半,才像"外圈")
- 幽灵项透明度
0.13 · (0.4 + settle·0.6),settle = 1 - min(1, |pos-near|·3)
——滚动中途更淡、落定时最清楚
- 每行内是 emoji + 词的两段结构(emoji 18px、词 700 22px 白字),
胶囊 #1a1c24 底 + 0 8px 24px rgba(20,22,40,.22) 投影 + overflow:hidden
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 词表 | 4 词 + emoji(Sales/Workflow/Admin/Reports),3 次切换 | 词长差决定挤开幅度的观感:demo 最短 5 字母最长 8 字母;差 >2 倍胶囊跳变太猛,句子重心左右甩 | | 切换点 | [0.25, 0.47, 0.69],窗 0.12(≈18f) | 段间约 0.10(≈15f)静置读词;起手 0.25 前的静置让观众先认出句式 | | 缓动 | E.inOutCubic | 两头缓才像滚轮起停;outCubic(只后缓)读作被甩上来,胶囊宽度也会跟着突变 | | 胶囊 | 高 48px、圆角 99、#1a1c24、宽 = 文本宽 + 74 | +74 是左右内边距 + emoji 位;<50 词贴边,>100 胶囊胖成按钮 | | 幽灵项 | 上 -38 / 下 58px,基础透明度 0.13 | 0.13 是"看得见但不抢"的甜点;>0.25 读作三行并列文字,不是幽灵;删了列表暗示就没了 | | 幽灵视差 | (pos-near)·48·0.5(半速) | 半速是"外圈更远"的立体感来源;同速则三行像一整块滑动,幽灵失去意义 | | 句子字号 | 两侧 30px/800、胶囊内 22px/700 | 胶囊内比句子小是正确层级(徽章依附句子);等大或更大就读作两个标题打架 | | 配色 | 底 #fbfbfd、句子 #15171d、胶囊白字 | 浅底 + 单一深色块是本卡的身份证;深底版会让胶囊消失,需整套重配 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 宽度靠 setup 期
offsetWidth实测:字体没加载完时量到的是回退字体
的宽度,胶囊会偏窄或偏宽——移植时须确保 webfont 就绪后再量,或改用 离屏 canvas measureText;length·13 那条兜底只保证不崩,不保证准
- 词长差要控制在 2 倍内:宽度自适应是本卡的卖点也是风险,
Sales→Workflow 这一跳已经是可接受上限,再大句子两侧甩得晃眼
- 两侧文字必须
flex:none:不然 flex 会去压缩它们而不是移动它们,
"挤开"变成"字被压扁"
- 幽灵项文本取
round(pos)的邻居,所以在切换的正中间(pos=x.5)会
跳一次文本——被 settle 压到最淡时发生,肉眼基本不可察;但若把 透明度调高到 0.25+,这一跳就会露出来
- emoji 在不同平台字形宽度不一(尤其
⚙️带变体选择符),会让实测宽度
漂移几像素;实战建议换成项目自己的单色图标,别依赖系统 emoji
- 与 vertical-word-roll-blur-cycle 分工:那卡宽度固定、裸词、带滚筒 blur
(词是句子成分),本卡宽度自适应、深色胶囊、带幽灵项(词是徽章)—— 同片只留一个;声音上三次落定是三个节奏点,配轻质开关/胶囊贴合音, 连发交替双样本,配法纪律见 sound-design.md
参考实现
demos/typography/pill-chip-slot-cycle-handled/ (PillChipSlotCycleHandled.tsx)
Mã nguồn tham chiếu (Remotion)
- demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.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.
- 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.
- Clone repo video-shotcraft; component import fixture chung từ demos/_fixtures và helper từ assets/lib.
- Đă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/pill-chip-slot-cycle-handled.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("pill-chip-slot-cycle-handled")Prompt
Use the video-shotcraft shot recipe "Viên chip cuộn slot" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/pill-chip-slot-cycle-handled.json Raw TSX: https://gimgs.net/motion/pill-chip-slot-cycle-handled/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 5.5s 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/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx =====
// pill-chip-slot-cycle-handled — Chip Slot Cycle 胶囊滚轮挤开(motion-lab 定稿转原生 Remotion)
// 白底句式 "Your [chip] Handled":深色胶囊内词垂直滚轮轮换(Sales→Workflow→Admin→Reports),
// 上下露出灰色幽灵项,胶囊宽度随词长平滑变化,两侧文字被自然挤开收拢。
// 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。
import React, { useLayoutEffect, useRef, useState } from 'react';
import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';
export const PILL_CHIP_SLOT_CYCLE_HANDLED_DURATION = 150; // 5000ms @30fps
const WORDS = [
{ w: 'Sales', e: '⚡' },
{ w: 'Workflow', e: '📈' },
{ w: 'Admin', e: '⚙️' },
{ w: 'Reports', e: '📄' },
];
const FONT = '700 22px -apple-system,system-ui,sans-serif';
// 兜底词宽(原 effect.js 未挂载时的估算:字符数×13,再加胶囊内留白 74);
// 首帧 useLayoutEffect 用 offsetWidth 实测替换,与原渲染逐像素一致
const FALLBACK_WIDTHS = WORDS.map((o) => o.w.length * 13 + 74);
// 两侧静态文字("Your" / "Handled")
const SIDE: React.CSSProperties = {
color: '#15171d',
fontWeight: 800,
fontSize: 30,
letterSpacing: -0.5,
flex: 'none',
};
export const PillChipSlotCycleHandled: React.FC = () => {
const t = useT();
// 量宽:与原 effect.js 相同,用隐藏 span 实测每个词的 offsetWidth(+74 胶囊留白),
// 只在首次布局量一次;量到前用兜底估算,避免首帧闪动
const measRef = useRef<HTMLDivElement>(null);
const [widths, setWidths] = useState<number[]>(FALLBACK_WIDTHS);
useLayoutEffect(() => {
const spans = measRef.current?.children;
if (!spans) return;
setWidths(
WORDS.map((o, i) => ((spans[i] as HTMLElement).offsetWidth || o.w.length * 13) + 74),
);
}, []);
// 三次切换:0.25 / 0.47 / 0.69,每次用 0.12 的 inOutCubic 推进一格
let pos = 0;
for (const s0 of [0.25, 0.47, 0.69]) pos += seg(t, s0, s0 + 0.12, E.inOutCubic);
const ci = Math.min(WORDS.length - 1, Math.floor(pos));
const frac = pos - ci;
// 胶囊宽度随当前词→下一词插值,平滑挤开两侧文字
const chipW = lerp(frac, widths[ci], widths[Math.min(ci + 1, WORDS.length - 1)]);
// 幽灵项:当前词的前/后邻居,随滚动微移,落定时回到基准透明度
const near = Math.round(pos);
const roll = (pos - near) * 48 * 0.5;
const settle = 1 - Math.min(1, Math.abs(pos - near) * 3);
const ghost = (top: number): React.CSSProperties => ({
position: 'absolute',
left: '50%',
top,
font: FONT,
color: '#15171d',
whiteSpace: 'nowrap',
transform: `translateX(-50%) translateY(${-roll}px)`,
opacity: 0.13 * (0.4 + settle * 0.6),
});
return (
<DesignStage bg="#fbfbfd">
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontFamily: '-apple-system,system-ui,sans-serif',
}}
>
{/* 隐藏量宽 span(与原 setup 的 meas 等价,不参与布局显示) */}
<div ref={measRef} style={{ position: 'absolute', visibility: 'hidden' }}>
{WORDS.map((o) => (
<span key={o.w} style={{ whiteSpace: 'nowrap', font: FONT }}>
{o.w}
</span>
))}
</div>
<div style={SIDE}>Your</div>
<div style={{ position: 'relative', flex: 'none' }}>
{/* 深色胶囊:overflow hidden 里放 4 行词的滚轮列 */}
<div
style={{
position: 'relative',
height: 48,
width: chipW,
margin: '0 14px',
flex: 'none',
borderRadius: 99,
background: '#1a1c24',
boxShadow: '0 8px 24px rgba(20,22,40,.22)',
overflow: 'hidden',
}}
>
<div
style={{
position: 'absolute',
left: 0,
right: 0,
top: 0,
transform: `translateY(${-pos * 48}px)`,
}}
>
{WORDS.map(({ w, e }) => (
<div
key={w}
style={{
height: 48,
display: 'flex',
alignItems: 'center',
gap: 9,
paddingLeft: 18,
whiteSpace: 'nowrap',
}}
>
<span style={{ fontSize: 18 }}>{e}</span>
<span style={{ font: FONT, color: '#fff' }}>{w}</span>
</div>
))}
</div>
</div>
{/* 胶囊外上下的灰色幽灵项 */}
<div style={ghost(-38)}>{near > 0 ? WORDS[near - 1].w : ''}</div>
<div style={ghost(58)}>{near < WORDS.length - 1 ? WORDS[near + 1].w : ''}</div>
</div>
<div style={SIDE}>Handled</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/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.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 |
|---|---|---|---|
| — | |||
Cùng danh mục

Ba từ tiếp sức bằng hình học
Ba từ lợi ích mỗi từ mang riêng một bộ hình học tiếp sức — vòng tròn nét đứt tự xoay thu nhỏ → ba vòng tròn nét liền mọc lên lần lượt → ánh kim sheen quét qua rồi thu về trắng thuần; mỗi từ mới viền nét rồi tô đầy.

Tiếp sức từ với cuộn phim trang
Cột trái các thẻ trang đen trắng cuộn theo bước, bên phải từ lớn chữ serif tiếp sức tại chỗ (danh từ cố định + động từ luân phiên) — chỉ khi đổi từ mới cuộn một bước, khớp chính xác điểm giữa thẻ trang hiện tại.

Cuộn từ dọc theo nhịp
Từ cuối câu đổi trên cuộn dọc, 3 lần đổi từ mỗi lần 0.55s, nhanh trước chậm sau kèm hơi overshoot; các dòng lân cận nhòe dọc và xám dần theo khoảng cách, từ ở giữa vừa đậu liền nhuộm từ xám sang màu nhấn.

Hai cách tiết lộ code song song
Cùng một khối code tô cú pháp được tiết lộ hai cách song song — trái: stagger cấp dòng mờ vào nổi lên 8px, phải: gõ từng ký tự nhưng giữ nguyên màu token gốc, con trỏ vuông #3a4468 đệm dưới ký tự hiện tại.