// ===== 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 ( {/* 纸面底 —— 与原采集页 paper() 容器一致 */}
{/* 选择器视口:300×(34×5) 居中,整体在开头 5% 内淡入 */}
{/* 焦点药丸:位置不动,仅落定时 scaleY 呼吸。 原采集页是 content-box,带 1px 边框需显式声明,否则高度差 2px */}
{/* 内容列:整列 translateY 穿过焦点药丸 */}
{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 (
{/* 图标仅在贴近焦点时浮现(d<0.625 内可见) */} {ICONS[i]} {txt}
); })}
{/* 上下渐隐罩:纸色→透明→纸色,盖在列表之上 */}
{/* 左外侧固定 AI 徽标:黑底白字方形圆角,开头略滞后淡入 */}
AI
); };