Motion · Interaction
Chip Lift to User Pill
A target chip in a grid hard-cuts to inverted black-on-white in 3 frames, other chips fade and shrink out staggered by Manhattan distance to it; the black chip's left edge anchors and grows into a pill, typing out a name letter by letter and lighting a green dot, then a 1px line connects it to a circular badge.
Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player
When to use
The "pick and expand this one from a pile of candidates" interaction flow; feature demos for collaboration/address-book/recipient-style products; a select-to-detail transition.
Intention
Give "selecting" two completely different textures: the instant of selection is hard (3-frame stepped inversion, like real UI's :active), and everything after is soft (the pill growing, letters typing in, the green dot lighting up). Other chips fade out staggered by distance, telling viewers "attention is converging from here" — ordering by distance matters more than ordering by time, because it gives the causality a spatial anchor.
Duration & energy
- Duration: About 5.0s (150f@30fps).
- Energy: Medium (the moment of selection is a hard burst, everything after is an unhurried grow-and-type).
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
让"选中"这件事有两段完全不同的质感:选中瞬间是硬的(3 帧台阶化反色, 像真实 UI 的 :active),选中之后是软的(药丸生长、逐字打字、绿点点亮)。 其余 chip 按距离交错淡出,是在告诉观众"注意力从这里开始收拢"—— 距离排序比时间排序重要,因为它给出了空间上的因果。
动效核心
- 4×3 网格(chip 40×24、间距 10/9),目标位于第 2 列第 2 行,
选中前先 appendChild 提到最上层
- 反色硬切:
a = seg(0.04→0.085, linear)再台阶化成
a<0.34 ? 0 : a<0.67 ? 0.5 : 1——只有 3 个取值,得到 3 帧硬切感; 背景 #fff→INK、描边 LINE→INK、文字 TXT→#fff 同步跳变
- 距离交错淡出:其余 chip 按曼哈顿距离
|Δcol|+|Δrow|,
d0 = 0.10 + dist*0.022,各 0.075 走完(outQuad), opacity 1→0 + scale 1→0.9
- 左缘锚定生长:
g = seg(0.26→0.44, outCubic)驱动 width 40→190px,
left 不变——生长方向单一,读作"展开"而非"移动"
- 人名逐字:以
g为自变量(不是 t),字符0.18 + i*0.062起、
各 0.05 走完,translateY 2→0;原 2 字母标签在 g*5 内先撤掉
- 绿点
#35D07F+0 0 8px辉光,在g的 0.85→1 段 outBack 弹出,
left = w − 15 始终吸在药丸右端
- 连接线
0.47→0.57outQuad 从 0 拉到 90px → 徽标0.56→0.63
scale 0.8→1 淡入 → 字幕 0.6→0.66 出现并逐词加深到 0.92
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 反色时长 | t 0.04→0.085,台阶化成 3 档 | 不要给它缓动:连续插值读作"渐变高亮",台阶才读作按下 | | 距离间隔 | 0.022 / 曼哈顿距离 | 按 index 排序也能错峰,但按距离才有"从选中点扩散"的空间因果 | | 余项退场 | 各 0.075、opacity→0 + scale→0.9 | 只淡出不缩小会显得"被擦掉";缩到 0.9 才像退到后面 | | 药丸生长 | width 40→190px,t 0.26→0.44 outCubic,左缘锚定 | 双向生长(居中扩张)会把它读成弹窗,不是同一个 chip 长大 | | 逐字打字 | 以 g 为自变量,间隔 0.062、各 0.05 | 挂在 g 上而不是 t 上:改生长时长时打字节奏自动跟随 | | 绿点 | 7px + 0 0 8px 辉光,g 的 0.85→1 段 outBack | 必须在生长收尾才亮:早了就成了"已在线",不是"刚接上" | | 连接线 | 0→90px,t 0.47→0.57 outQuad | 线要在药丸完全定型后才起笔,否则读作药丸在被拽 | | 徽标 | 26px,0.56→0.63 outCubic,scale 0.8→1 | 与连接线抵达重叠 0.01,才有"线到即物到"的交棒 |
已知坑
- 目标 chip 必须在动画前提到 DOM 最上层,否则生长时被右侧 chip 盖住
- 药丸内的人名容器
left:13px是按 24px 高的 chip 配的内边距;
改 chip 高度要同改,否则文字贴边
- 绿点的
left = w − 15每帧重算——写死坐标后一改 PW1 就飞出药丸 - 连接线与徽标的坐标全部基于
PX + PW1(药丸终态右缘),
改生长终值必须同改这两处
- 占位内容:12 个双字母标签 + 人名 "Casey Doe" + 字幕
"Starting with Casey",落地全部替换;人名字符数变化会改变打字总时长 (间隔 0.062 × 字符数必须留在 g 的 1.0 以内)
参考实现
demos/interaction/chip-lift-to-user-pill/ (ChipLiftToUserPill.tsx)
Reference implementation (Remotion)
- demos/interaction/chip-lift-to-user-pill/ChipLiftToUserPill.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/chip-lift-to-user-pill/ChipLiftToUserPill.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/chip-lift-to-user-pill.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/interaction/chip-lift-to-user-pill/ChipLiftToUserPill.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("chip-lift-to-user-pill")Prompt
Use the video-shotcraft shot recipe "Chip Lift to User Pill" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/chip-lift-to-user-pill.json Raw TSX: https://gimgs.net/motion/chip-lift-to-user-pill/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/interaction/chip-lift-to-user-pill/ChipLiftToUserPill.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/interaction/chip-lift-to-user-pill/ChipLiftToUserPill.tsx =====
// chip-lift-to-user-pill — Chip Lift 选中 chip 长成人名药丸(motion-lab 定稿转原生 Remotion)
// 网格里的目标 chip 先 3 帧硬切反色成黑底白字,其余 chip 按到它的距离交错淡出并缩到 0.9;
// 黑 chip 保持左缘不动向右生长成药丸,内部逐字打出人名并点亮绿点,再拉一条 1px 连接线
// 接到圆形徽标,最后走逐词加深字幕。
// 设计坐标 480×270(DesignStage 等比放大),440×240 定尺画布居中排版。
import React from 'react';
import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';
export const CHIP_LIFT_TO_USER_PILL_DURATION = 150; // 5000ms @30fps
// ---- 本卡共享量(浅灰瑞士极简系配色 / 字体;BG/DIM/h2r 取自 motion-lab/fx/b09.js 同批定义) ----
const SANS = '-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif';
const BG = '#F1F1F3'; // 页面浅灰
const INK = '#0B0B0C'; // 纯黑
const TXT = '#111111'; // 正文黑
const DIM = '#C9C9CE'; // 浅灰占位字
const LINE = '#E6E6EA'; // 描边
const clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);
const h2r = (h: string) => [parseInt(h.slice(1, 3), 16), parseInt(h.slice(3, 5), 16), parseInt(h.slice(5, 7), 16)];
// 颜色插值:mix(p,'#C9C9CE','#111')
const mix = (p: number, a: string, b: string) => {
const A = h2r(a), B = h2r(b), q = clamp01(p);
return `rgb(${Math.round(A[0] + (B[0] - A[0]) * q)},${Math.round(A[1] + (B[1] - A[1]) * q)},${Math.round(A[2] + (B[2] - A[2]) * q)})`;
};
// ---- 网格参数(与 effect.js 完全一致) ----
const COLS = 4, ROWS = 3, CW = 40, CH = 24, GX = 10, GY = 9, GX0 = 8, GY0 = 70;
const TC = 1, TR = 1; // 目标 chip 的列/行
const LABELS = ['JD', 'MK', 'CD', 'RL', 'AV', 'TP', 'KN', 'BW', 'CE', 'HR', 'LM', 'DQ'];
// 目标 chip 左上角坐标(左缘锚定,生长时不动)
const TX = GX0 + TC * (CW + GX); // 58
const TY = GY0 + TR * (CH + GY); // 103
// 其余 chip:位置 + 到目标的曼哈顿距离(交错淡出用)
const OTHERS = Array.from({ length: ROWS * COLS }, (_, i) => {
const r = Math.floor(i / COLS), c = i % COLS;
return {
x: GX0 + c * (CW + GX),
y: GY0 + r * (CH + GY),
label: LABELS[i],
dist: Math.abs(c - TC) + Math.abs(r - TR),
isT: c === TC && r === TR,
};
}).filter((o) => !o.isT);
const PW0 = CW, PW1 = 190; // 药丸生长的起止宽度
const NAME_CHARS = 'Casey Doe'.split(''); // 药丸内逐字打出的人名
// 结尾字幕(逐词加深语法,只用到 show + inn 两态)
const CAP_WORDS = 'Starting with Casey'.split(' ');
const CAP_ST = 0.78 / CAP_WORDS.length;
const CAP_WIN = CAP_ST * 1.5;
// 徽标尺寸(白底圆 + 四角星)
const BADGE_SIZE = 26;
const BADGE_SVG = Number((BADGE_SIZE * 0.52).toFixed(1)); // 13.5
export const ChipLiftToUserPill: React.FC = () => {
const t = useT();
// A 反色硬切(3 帧感):进度台阶化到 0 / 0.5 / 1 → 硬切质感
const a = seg(t, 0.04, 0.085, E.linear);
const aq = a < 0.34 ? 0 : a < 0.67 ? 0.5 : 1;
// C 药丸从左缘生长 + 逐字 + 绿点
const g = seg(t, 0.26, 0.44, E.outCubic);
const w = lerp(g, PW0, PW1);
const dq = seg(g, 0.85, 1, E.outBack);
// D 连接线 → 徽标 → 字幕
const cw = seg(t, 0.47, 0.57, E.outQuad);
const bp = seg(t, 0.56, 0.63, E.outCubic);
const capShow = seg(t, 0.6, 0.66);
const capInn = seg(t, 0.6, 0.92);
return (
<DesignStage bg={BG}>
{/* 页面 + 440×240 定尺画布(居中) */}
<div
style={{
position: 'absolute',
inset: 0,
background: BG,
overflow: 'hidden',
fontFamily: SANS,
WebkitFontSmoothing: 'antialiased',
}}
>
<div style={{ position: 'absolute', left: '50%', top: '50%', width: 440, height: 240, margin: '-120px 0 0 -220px' }}>
{/* B 其余 chip:按到目标的曼哈顿距离交错淡出 + scale .9 */}
{OTHERS.map((o, i) => {
const d0 = 0.10 + o.dist * 0.022;
const p = seg(t, d0, d0 + 0.075, E.outQuad);
return (
<div
key={i}
style={{
position: 'absolute',
left: o.x,
top: o.y,
width: CW,
height: CH,
borderRadius: CH / 2,
background: '#fff',
border: `1px solid ${LINE}`,
display: 'flex',
alignItems: 'center',
boxSizing: 'border-box',
overflow: 'hidden',
boxShadow: '0 1px 3px rgba(0,0,0,.04)',
opacity: 1 - p,
transform: `scale(${lerp(p, 1, 0.9)})`,
}}
>
<div
style={{
position: 'absolute',
left: 0,
top: 0,
width: CW,
height: CH,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
font: `600 10.5px/1 ${SANS}`,
letterSpacing: '.6px',
color: TXT,
}}
>
{o.label}
</div>
</div>
);
})}
{/* 目标 chip(最上层):反色硬切 → 左缘锚定向右生长成药丸 */}
<div
style={{
position: 'absolute',
left: TX,
top: TY,
width: w,
height: CH,
borderRadius: CH / 2,
background: mix(aq, '#ffffff', INK),
border: `1px solid ${mix(aq, LINE, INK)}`,
display: 'flex',
alignItems: 'center',
boxSizing: 'border-box',
overflow: 'hidden',
boxShadow: '0 1px 3px rgba(0,0,0,.04)',
}}
>
{/* 原缩写标签:反色后随生长淡出 */}
<div
style={{
position: 'absolute',
left: 0,
top: 0,
width: CW,
height: CH,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
font: `600 10.5px/1 ${SANS}`,
letterSpacing: '.6px',
color: mix(aq, TXT, '#ffffff'),
opacity: 1 - clamp01(g * 5),
}}
>
CD
</div>
{/* 人名逐字打出(stagger 跑在生长进度 g 上) */}
<div
style={{
position: 'absolute',
left: 13,
top: 0,
height: CH,
display: 'flex',
alignItems: 'center',
whiteSpace: 'nowrap',
}}
>
{NAME_CHARS.map((ch, i) => {
const p = seg(g, 0.18 + i * 0.062, 0.18 + i * 0.062 + 0.05, E.outQuad);
return (
<span
key={i}
style={{
font: `600 11px/1 ${SANS}`,
color: '#fff',
opacity: p,
whiteSpace: 'pre',
letterSpacing: '.2px',
transform: `translateY(${lerp(p, 2, 0)}px)`,
display: 'inline-block',
}}
>
{ch}
</span>
);
})}
</div>
{/* 在线绿点:outBack 弹出,钉在药丸右缘内侧 */}
<div
style={{
position: 'absolute',
top: (CH - 7) / 2,
left: w - 15,
width: 7,
height: 7,
borderRadius: '50%',
background: '#35D07F',
boxShadow: '0 0 8px rgba(53,208,127,.6)',
opacity: clamp01(dq * 2),
transform: `scale(${dq})`,
}}
/>
</div>
{/* 1px 连接线:从药丸右缘拉到徽标 */}
<div
style={{
position: 'absolute',
left: TX + PW1,
top: TY + CH / 2,
height: 1,
width: `${(cw * 90).toFixed(1)}px`,
background: TXT,
}}
/>
{/* AI 徽标(白底圆 + 四角星) */}
<div
style={{
position: 'absolute',
width: BADGE_SIZE,
height: BADGE_SIZE,
borderRadius: '50%',
background: '#fff',
border: `1px solid ${LINE}`,
boxSizing: 'border-box',
boxShadow: '0 2px 10px rgba(0,0,0,.07)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
left: TX + PW1 + 90 - 2,
top: TY + CH / 2 - 13,
opacity: bp,
transform: `scale(${lerp(bp, 0.8, 1)})`,
}}
>
<svg width={BADGE_SVG} height={BADGE_SVG} viewBox="0 0 24 24">
<path d="M12 0.8 L14.3 9.7 L23.2 12 L14.3 14.3 L12 23.2 L9.7 14.3 L0.8 12 L9.7 9.7 Z" fill={TXT} />
</svg>
</div>
{/* 结尾字幕:逐词加深(浅灰占位 → 黑),整行透明度随 show 淡入 */}
<div
style={{
position: 'absolute',
display: 'flex',
alignItems: 'baseline',
whiteSpace: 'nowrap',
left: TX + PW1 - 18,
top: TY + CH + 34,
opacity: capShow,
}}
>
{CAP_WORDS.map((wd, i) => {
const q = clamp01((capInn - i * CAP_ST) / CAP_WIN);
return (
<span
key={i}
style={{
font: `600 13px/1.25 ${SANS}`,
color: mix(q, DIM, TXT),
letterSpacing: (-0.03 * (1 - q)).toFixed(4) + 'em',
marginRight: i === CAP_WORDS.length - 1 ? 0 : 4.5,
}}
>
{wd}
</span>
);
})}
</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/interaction/chip-lift-to-user-pill/ChipLiftToUserPill.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

Live voice waveform
A live waveform in a recording pill — 64 thin bars rise and fall with "speech," spiking tall mid-speech and shrinking to a dotted line on pauses, scrolling right to left; a full speak-pause-speak-submit performance.

Type, filter, drill in
Typing a search on a real UI, the grid converges to a single card on its own, then a click drills through into the detail page.

Theme switch: sweep and ripple
Two theme-switch moves — theme-sweep, a diagonal wipe (the skin changes wherever the boundary passes), and palette-ripple, a combo move (a ⌘K panel collapses to a point, a ripple spreads from it to re-skin).
Segmented thumb hero
A segmented control's thumb shift becomes the star of a close-up — a cursor clicks it and a new icon springs in.