// ===== demos/typography/typing-code-block/TypingCodeBlock.tsx ===== // typing-code-block — Code Block Reveal 代码块揭示(motion-lab 定稿转原生 Remotion) // 同一段语法高亮代码的两种 reveal 对照:左侧逐行淡入上浮(行级 stagger), // 右侧逐字符打字但保持 token 着色,当前字符位以方块底色块提示光标。 // 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。 import React from 'react'; import { DesignStage, E, seg, useT } from '../../_fixtures/Motion'; export const TYPING_CODE_BLOCK_DURATION = 138; // 4600ms @30fps // token 配色:关键字/标识符/函数/字符串/标点/注释 const K = '#c792ea'; const ID = '#e8eaf0'; const FN = '#82aaff'; const ST = '#c3e88d'; const PU = '#89ddff'; const CM = '#546e7a'; // 每行是 [text, color] token 序列 const LINES: [string, string][][] = [ [['const ', K], ['app', ID], [' = ', PU], ['createApp', FN], ['();', ID]], [['app', ID], ['.', PU], ['use', FN], ['(', ID], ['router', ID], [');', ID]], [['app', ID], ['.', PU], ['mount', FN], ['(', ID], ["'#root'", ST], [');', ID]], [['// ready', CM]], ]; // 右侧打字用的逐字符扁平序列(保留 token 颜色 + 行号) const FLAT: { ch: string; color: string; row: number }[] = []; LINES.forEach((line, row) => { for (const [txt, color] of line) for (const ch of txt) FLAT.push({ ch, color, row }); }); // 代码面板容器:左右两块共用的外框 + 顶部小标签 const Panel: React.FC<{ x: number; label: string; children: React.ReactNode }> = ({ x, label, children, }) => (
{label}
{children}
); export const TypingCodeBlock: React.FC = () => { const t = useT(); // 右侧打字进度:t∈[0.08,0.9] 线性推进到全部字符 const typed = Math.floor(seg(t, 0.08, 0.9) * FLAT.length); return ( {/* 左:逐行淡入上浮(行级 stagger) */} {LINES.map((line, i) => { const k = seg(t, 0.08 + i * 0.14, 0.08 + i * 0.14 + 0.3, E.outCubic); return (
{line.map(([txt, color], j) => ( {txt} ))}
); })}
{/* 右:逐字符打字(保色),当前字符位带方块光标底色 */} {LINES.map((_, row) => (
{FLAT.map((c, i) => c.row === row ? ( {c.ch} ) : null, )}
))}
); };