// ===== demos/effects/scanline-annotate-focus/ScanlineAnnotateFocus.tsx ===== // scanline-annotate-focus — Scanline Annotate 扫描分析取景标注(motion-lab 定稿转原生 Remotion) // 一条亮扫描线自上而下掠过页面,扫过之处按先后顺序弹出相机取景框:四角括号从约 1.75 倍 // 大小快速收拢对准目标区块(对准瞬间轻微过冲再回稳),随后旁侧打出等宽小字标注。 // 顶部状态行同步计数 00/06→06/06,扫完切换 ANALYSIS · COMPLETE。 // 页面内容为中性占位模板,标注词与强调色均可按项目替换。设计坐标 480×270(DesignStage 等比放大)。 import React from 'react'; import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion'; export const SCANLINE_ANNOTATE_FOCUS_DURATION = 138; // 4600ms @30fps const MONO = "'SF Mono',Menlo,Consolas,monospace"; const SERIF = "Georgia,'Times New Roman',serif"; const ACCENT = '#9fb6e8'; // 模板强调色,可按项目替换 const A_RGB = '159,182,232'; /* ---- 分析目标(bbox 手动微调留边):ft=取景框触发时刻,按扫描顺序推导 ---- */ type Target = { x: number; y: number; w: number; h: number; label: string; lx: number; ly: number; ft: number }; const TARGETS: Target[] = (() => { const ts: Target[] = [ { x: 18, y: 30, w: 108, h: 22, label: 'LOGO · MARK + WORDMARK', lx: 132, ly: 38, ft: 0 }, { x: 292, y: 48, w: 170, h: 158, label: 'MODULE · KINETIC TYPE', lx: 292, ly: 36, ft: 0 }, { x: 18, y: 58, w: 242, h: 78, label: 'H1 · SERIF DISPLAY', lx: 266, ly: 92, ft: 0 }, { x: 18, y: 160, w: 136, h: 32, label: 'CTA · PRIMARY + GHOST', lx: 160, ly: 172, ft: 0 }, { x: 14, y: 240, w: 224, h: 18, label: 'FOOTER · LEGAL', lx: 242, ly: 246, ft: 0 }, { x: 348, y: 235, w: 116, h: 23, label: 'SOCIAL · BRAND VOICE', lx: 348, ly: 224, ft: 0 }, ]; // 触发时刻:扫描线(0.06→0.66 纵扫 -30→300)越过 bbox 下缘 const rawT = (tg: Target) => 0.06 + ((tg.y + tg.h + 30) / 330) * 0.6; let prev = -1; for (const tg of [...ts].sort((a, b) => a.y + a.h - (b.y + b.h))) { tg.ft = Math.max(rawT(tg), prev + 0.05); // 依序钳制最小间隔 prev = tg.ft; } return ts; })(); // 取景框四角括号:每角朝外的两条边框 // 注意 boxSizing content-box:原采集页 9×9 内容 + 1.5px 外扩边框(外框 10.5), // Remotion 模板全局 border-box 会把边框内收导致括号臂变短、右/下角内移。 const C_BORDER = '1.5px solid #f2f3f5'; const CORNERS: React.CSSProperties[] = [ { left: 0, top: 0, borderTop: C_BORDER, borderLeft: C_BORDER }, { right: 0, top: 0, borderTop: C_BORDER, borderRight: C_BORDER }, { left: 0, bottom: 0, borderBottom: C_BORDER, borderLeft: C_BORDER }, { right: 0, bottom: 0, borderBottom: C_BORDER, borderRight: C_BORDER }, ]; export const ScanlineAnnotateFocus: React.FC = () => { const t = useT(); // 扫描线纵扫 + 首尾淡入淡出 const ly = lerp(seg(t, 0.06, 0.66), -30, 300); const lineOpacity = seg(t, 0.04, 0.09) * (1 - seg(t, 0.66, 0.71)); // 已触发的取景框计数(a>0 即已弹出) const fired = TARGETS.reduce((acc, tg) => acc + (seg(t, tg.ft, tg.ft + 0.11, E.outCubic) > 0 ? 1 : 0), 0); const done = seg(t, 0.74, 0.8); return ( {/* ---- 中性占位页面(静态底) ---- */}
{/* 顶栏(url pill + 状态)——对照原样片实测整体高 0.5px(亚像素基线取整差), 分数 top 会被取整吞掉,改用 translateY 补偿(transform 不参与像素取整) */}
app.example.com
200 OK · TLS
{/* logo:点阵 mark + 衬线字标 */}
{[0, 1, 2, 3].map((i) => (
> 1) * 6 }} /> ))}
Acme Studio
{/* H1 两行 + 注脚 */}
{/* 两行 29px 衬线对照原样片实测低 0.5px(行盒取整差),translateY 补偿(分数 top 会被取整吞掉) */}
The headline for
your product here
H1 · UI-SERIF / GEORGIA
{/* CTA 行 */}
GET STARTED
DOCS
{/* 右侧模块卡(boxSizing content-box:原采集页边框外扩,内容盒保持 162×150) */}
WORK
04 / 08
sample
KINETIC TYPE · 04
{[0, 1, 2, 3].map((i) => (
))}
00:30
{/* 页脚 + 社交 chip */}
A PRODUCT OF ACME · ACME LABS, INC.
{/* boxSizing content-box:原采集页 11×11 内容 + 1px 外扩边框 */}
x
@USERNAME
{/* ---- 取景框 + 标注(按扫描顺序触发) ---- */} {TARGETS.map((tg, i) => { const a = seg(t, tg.ft, tg.ft + 0.11, E.outCubic); // 弹出进度 const s = lerp(E.outBack(seg(t, tg.ft, tg.ft + 0.13)), 1.75, 1); // 1.75 倍收拢 + 过冲回稳 const fillO = 0.07 * seg(t, tg.ft + 0.04, tg.ft + 0.09) * (1 - seg(t, tg.ft + 0.09, tg.ft + 0.22)); // 对准瞬间白色微闪 const la = seg(t, tg.ft + 0.05, tg.ft + 0.16, E.outCubic); // 标注淡入上移 return (
0 ? s : 1.75})`, }} > {CORNERS.map((c, k) => (
))}
{tg.label}
); })} {/* ---- 扫描线(渐变拖尾 + 亮芯) ---- */}
{/* ---- 顶部状态行:SCAN 计数 → ANALYSIS · COMPLETE ---- */}
= 1 ? ACCENT : '#8d93a0', opacity: seg(t, 0.03, 0.08), }} > {done >= 1 ? 'ANALYSIS · COMPLETE' : `SCAN · 0${fired}/0${TARGETS.length}`}
); };