// ===== demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx ===== // glass-pill-dictation-typing — Glass Pill Dictation 玻璃胶囊听写(motion-lab 定稿转原生 Remotion) // 纯黑底上一条定宽玻璃胶囊:整条以约 1.25 倍略大弹出后缓落到位,胶囊内部自左暗到右亮 // 铺一层强调色光(ACCENT 变量,默认紫);光标先行出现,随后打字出现占位句 // 「Speak or type here」,光随打字进度渐渐熄灭,收尾成中性深色玻璃条;右端是描边圆角 // 方块里的竖条声波图标——全片最安静的一拍。 // 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。 import React, { useLayoutEffect, useRef, useState } from 'react'; import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion'; export const GLASS_PILL_DICTATION_TYPING_DURATION = 50; // 1650ms @30fps // 模板强调色:实际使用时按项目品牌色替换这一个变量(内嵌光 + 外泛光共用) const ACCENT_RGB = '146,126,212'; const UI = '-apple-system,system-ui,"Segoe UI",sans-serif'; const PH = 46; // 胶囊高度 const ICON = 30; // 右端声波图标方块尺寸 const BASE = [13, 7, 10, 6, 9]; // 竖条基准高度(左高右低的听写图标) const TEXT = 'Speak or type here'; // 占位句,长度贴近原片(19→18 字符),打字节奏不变 export const GlassPillDictationTyping: React.FC = () => { const t = useT(); // 定宽:原片胶囊宽度约为整句文本宽度的 2 倍,不随词伸缩——挂载时实测一次整句宽度 const measRef = useRef(null); const [textW, setTextW] = useState(180); // 兜底估算值,useLayoutEffect 实测后覆盖(先于首帧绘制) useLayoutEffect(() => { if (measRef.current) setTextW(measRef.current.offsetWidth); }, []); const PW = Math.round(textW + 168); // 出场:整体略大(~1.25x)快速浮现,约 0.45s 内缓落到位 const s = lerp(seg(t, 0, 0.22, E.outCubic), 1.25, 1); // 打字:caret 先行(~t0.03),字符 t0.06→0.73 匀速铺完,尾段保持 const n = Math.floor(seg(t, 0.06, 0.73) * TEXT.length + 1e-6); const caretO = seg(t, 0.025, 0.045) * (1 - seg(t, 0.75, 0.8)); // 强调色光随打字进度渐熄;同步收掉描边亮度的一点富余 const g = 1 - seg(t, 0.08, 0.76, E.inOutQuad); return (
{/* 居中走 translateX:PW 为奇数时圆心在 .5 半像素上,left/flex 会被布局 取整偏 0.5 设计像素;transform 矩阵不吸附,能与原片圆心逐像素对齐 */}
{/* 内嵌强调色光:左暗右亮的渐层,随打字进度熄灭(原片光在胶囊内部,无外部光晕) */}
{TEXT.slice(0, n)}
{/* 声波竖条轻微呼吸(原片几乎静止,仅微动) */}
{BASE.map((h, i) => (
))}
{/* 隐藏测量条:与正文同字体同字距,量一次整句宽度以定胶囊定宽 */}
{TEXT}
); };