Motion · Tương tác
Picker cuộn chọn tính năng
Bộ chọn dọc kiểu di động — viên thuốc tiêu điểm đứng yên, nội dung trôi qua nó, mỗi mục hãm phanh rồi dừng hẳn.
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ảnh đọc lần lượt tên tính năng/tên cảnh; màn trình diễn tương tác "chọn một"; trình diễn control kiểu picker cho sản phẩm di động
Ý đồ
Tạo cảm giác vật lý cho việc "chọn": khung tiêu điểm cố định, chính nội dung đang cuộn qua và bị hút vào đó. Mỗi mục phải thực sự đứng yên vài khung hình sau khi dừng — cuộn liên tục đọc thành hoạt ảnh loading, chỉ có tạm dừng mới đọc thành "mục này đã được chọn". Suy giảm theo khoảng cách (độ trong suốt + cỡ chữ + độ xám cùng suy giảm) là cách duy nhất giữ ánh mắt người xem luôn khóa vào hàng giữa.
Thời lượng & năng lượng
- Thời lượng: Khoảng 3.6s (108f@30fps)
- Năng lượng: Trung bình (mỗi lần hãm là một điểm nhịp, 5 nhịp tiến đều)
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)
意图
把"选择"这件事的物理感做出来:焦点框是固定的,是内容在滚动并被吸住。 每项停下后必须真的静止几帧——连续滚动读作 loading 动画,停顿才读作 "这一项被选中了"。距离衰减(透明度 + 字号 + 灰度三通道同时衰减)是让 观众视线始终锁在中间那一行的唯一手段。
动效核心
- 视口 300×170px(5 行 × rowH 34),焦点药丸固定在第 3 行位置
(top: rowH*2),白底 + 1px #E3E3E6 描边 + 0 2px 8px rgba(0,0,0,.06)
- 全程
t 0.05→0.95匀速切成 STEPS=5 步;步内位移
outQuint(min(1, local/(1−HOLD))),HOLD=5/14 的尾段完全不动 (每步≈0.65s = 走位≈0.42s + 静止≈0.23s)
- 列表整体
translateY(rowH*2 − pos*rowH)——移动的是内容,药丸零位移 - 距离衰减(
d = |i − pos|)分三段:opacityd≤1时 1→0.55、
1<d≤2 时 0.55→0.18;fontSize 17→14px(按 d/2); color d<0.5 用 INK、d<1.5 用 MID、更远用 #B9B9BE
- 行内图标 opacity
max(0, 1 − d*1.6)——只有焦点行看得见图标 - 落定后药丸
scaleY 1 + sin(min(1, land/0.6)·π)*0.06呼吸一下
(land 只在 HOLD 段内 >0,即吸附完成才呼吸)
- 视口上下各 26% 的 PAPER→透明渐变遮罩,让进出项自然消隐
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 行高/可见行数 | rowH 34px × 5 行,焦点在第 3 行 | 可见行数必须是奇数,否则焦点行不在视觉正中 | | 步数 | STEPS=5,窗口 t 0.05→0.95 匀速 | 5 拍是 3.6s 的舒适上限;7 拍每拍就压到 0.46s 读不清 | | HOLD | 5/14 ≈ 0.357(每步尾段静止≈0.23s) | 命门:HOLD 归零就变成匀速滚动,完全丢掉"吸附"语义 | | 吸附缓动 | outQuint(作用在 local/(1−HOLD) 上) | outQuint 的急减速就是"被磁铁吸住"的手感,outCubic 太软 | | opacity 衰减 | 1 → 0.55(d≤1)→ 0.18(d≤2) | 相邻行留 0.55 是让观众感知"上下还有内容";降到 0.3 就孤立了 | | fontSize 衰减 | 17→14px,按 min(1, d/2) | 字号差是第二重景深;只做透明度会显得扁 | | 图标可见性 | max(0, 1 − d·1.6) | 只有焦点行带图标,是"当前项"的额外标记 | | 药丸呼吸 | scaleY 1→1.06→1,落定后 0.6·HOLD 内走完 | 0.06 已到上限;再大就读作药丸自己在动,抢了内容的戏 |
已知坑
- 药丸必须是
translateY零位移的固定层,scaleY呼吸也不能带
位移,否则焦点框漂移,"内容穿过焦点"的语义立刻失效
- 距离衰减写在
fontSize上会触发逐帧重排(行高固定所以布局不跳),
行高若改成 auto 就会抖;rowH 必须写死
- 渐变遮罩的颜色要与背景 PAPER 完全一致,差一档就露出边界带
- 左外侧 26×22 的方形 AI 徽标用
margin:-11px 0 0 -186px相对画面中心
定位;改视口宽度必须同改 186px
- ITEMS 是 7 条占位功能名("Data Cleanup" 等),落地换真实功能名时
字长变化会影响居中观感——单行超过 300px 就要缩字号而不是换行
参考实现
demos/interaction/picker-carousel-feature-cycle/ (PickerCarouselFeatureCycle.tsx)
Mã nguồn tham chiếu (Remotion)
- demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.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/picker-carousel-feature-cycle.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("picker-carousel-feature-cycle")Prompt
Use the video-shotcraft shot recipe "Picker cuộn chọn tính năng" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/picker-carousel-feature-cycle.json Raw TSX: https://gimgs.net/motion/picker-carousel-feature-cycle/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 4.1s 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/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx =====
// picker-carousel-feature-cycle — Picker Carousel 功能名吸附轮播(motion-lab 定稿转原生 Remotion)
// 移动端风竖向选择器:焦点药丸不动、内容穿过它,每项停靠 0.45s 且带明显 outQuint
// 减速吸附 + 4–5 帧静止;按到中心距离分层控制 opacity/字号/灰度,落定时药丸做
// scaleY 1→1.06→1 极轻呼吸,左外侧固定方形 AI 徽标。
// 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。
import React from 'react';
import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';
export const PICKER_CAROUSEL_FEATURE_CYCLE_DURATION = 108; // 3600ms @30fps
const PAPER = '#F3F3F1';
const INK = '#111113';
const MID = '#8A8A8F';
const ROW_H = 34; // 单行高度
const ITEMS = [
'Data Cleanup',
'Direct Message',
'Smart Segments',
'Batch Actions',
'Reward Program',
'Automated Flows',
'Variant Testing',
];
const ICONS = ['◎', '✉', '◧', '◈', '★', '↺', '⚑'];
const STEPS = 5; // 五次吸附推进
const HOLD = 5 / 14; // 每步末段静止占比(≈4–5 帧不动)
export const PickerCarouselFeatureCycle: React.FC = () => {
const t = useT();
// 主时间轴:t∈[0.05,0.95] 均分 5 步;步内前 (1-HOLD) 用 outQuint 吸附,末段静止
const g = seg(t, 0.05, 0.95) * STEPS;
const step = Math.min(STEPS - 1, Math.floor(g));
const local = Math.min(1, g - step);
const mv = E.outQuint(Math.min(1, local / (1 - HOLD)));
const pos = step + mv;
// 落定呼吸:吸附完成进入 HOLD 后,药丸 scaleY 1→1.06→1 极轻脉冲
const land = Math.max(0, (local - (1 - HOLD)) / HOLD);
const breath = land > 0 ? Math.sin(Math.min(1, land / 0.6) * Math.PI) * 0.06 : 0;
return (
<DesignStage bg={PAPER}>
{/* 纸面底 —— 与原采集页 paper() 容器一致 */}
<div
style={{
position: 'absolute',
inset: 0,
overflow: 'hidden',
background: PAPER,
fontFamily: "-apple-system,'Helvetica Neue',Helvetica,Arial,sans-serif",
}}
>
{/* 选择器视口:300×(34×5) 居中,整体在开头 5% 内淡入 */}
<div
style={{
position: 'absolute',
left: '50%',
top: '50%',
width: 300,
height: ROW_H * 5,
transform: 'translate(-50%,-50%)',
overflow: 'hidden',
opacity: seg(t, 0, 0.05),
}}
>
{/* 焦点药丸:位置不动,仅落定时 scaleY 呼吸。
原采集页是 content-box,带 1px 边框需显式声明,否则高度差 2px */}
<div
style={{
position: 'absolute',
left: 0,
right: 0,
top: ROW_H * 2,
height: ROW_H,
boxSizing: 'content-box',
borderRadius: 999,
background: '#fff',
border: '1px solid #E3E3E6',
boxShadow: '0 2px 8px rgba(0,0,0,.06)',
transform: `scaleY(${1 + breath})`,
}}
/>
{/* 内容列:整列 translateY 穿过焦点药丸 */}
<div
style={{
position: 'absolute',
left: 0,
right: 0,
top: 0,
transform: `translateY(${ROW_H * 2 - pos * ROW_H}px)`,
}}
>
{ITEMS.map((txt, i) => {
// 按到中心距离分层:透明度两段线性、字号 17→14、颜色三档灰度
const d = Math.abs(i - pos);
const k2 = Math.min(2, d);
const o = k2 <= 1 ? lerp(k2, 1, 0.55) : lerp(k2 - 1, 0.55, 0.18);
return (
<div
key={i}
style={{
height: ROW_H,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
fontWeight: 600,
fontSize: lerp(Math.min(1, d / 2), 17, 14),
lineHeight: 1,
fontFamily: "-apple-system,'Helvetica Neue',sans-serif",
color: d < 0.5 ? INK : d < 1.5 ? MID : '#B9B9BE',
opacity: o,
}}
>
{/* 图标仅在贴近焦点时浮现(d<0.625 内可见) */}
<span style={{ fontSize: 14, opacity: Math.max(0, 1 - d * 1.6) }}>
{ICONS[i]}
</span>
<span>{txt}</span>
</div>
);
})}
</div>
{/* 上下渐隐罩:纸色→透明→纸色,盖在列表之上 */}
<div
style={{
position: 'absolute',
inset: 0,
pointerEvents: 'none',
background: `linear-gradient(180deg,${PAPER} 0%,rgba(243,243,241,0) 26%,rgba(243,243,241,0) 74%,${PAPER} 100%)`,
}}
/>
</div>
{/* 左外侧固定 AI 徽标:黑底白字方形圆角,开头略滞后淡入 */}
<div
style={{
position: 'absolute',
left: '50%',
top: '50%',
margin: '-11px 0 0 -186px',
width: 26,
height: 22,
borderRadius: 6,
background: INK,
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 700,
fontSize: 10,
lineHeight: 1,
fontFamily: '-apple-system,sans-serif',
letterSpacing: 0.5,
opacity: seg(t, 0.02, 0.09),
}}
>
AI
</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/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.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

Sóng âm giọng nói trực tiếp
Sóng âm trực tiếp trên viên nang ghi âm — 64 cột dọc mảnh nhấp nhô theo "lời nói", giữa lúc nói vươn cao, ngừng thì co lại thành dòng chấm, sóng cuộn từ phải sang trái; trọn màn nói→ngừng→nói→gửi.

Gõ, lọc, đi vào chi tiết
Gõ tìm kiếm trên UI thật, lưới tự thu về một thẻ duy nhất, rồi cú click xuyên vào trang chi tiết.

Đổi theme: quét dọc và gợn sóng
Hai kiểu đổi theme — theme-sweep quét chéo (biên quét qua đâu đổi da đến đó) và palette-ripple kiểu kết hợp (panel ⌘K co lại thành điểm, gợn sóng lan ra từ điểm đó đổi da).
Thumb điều khiển phân đoạn làm nhân vật chính
Cú dịch chuyển thumb trong control phân đoạn thành nhân vật chính cận cảnh — con trỏ bấm vào, thumb trắng trượt qua, icon mới bật lên.