// blinds-slice|百叶窗切条错峰擦除 // FakeDashboard A → B。12 根 160px 竖条,从左到右 delay=列号×2f, // 每条 10f 内完成翻换:条内 A scaleX 1→0(origin 左缘)与 B scaleX 0→1 // (origin 右缘)共用同一进度 p(Easing.in(cubic)),交接缝恒等于 // x+160(1-p),数学上无露底。缝上亮线(柔光+暗描边+白核)随波扫过。 // 波 20–52f;52f 起摘罩(整页 B 直出、条结构与亮线全部卸载), // 52–150f 真静止 98f ≥ 40f。帧确定,无随机源。 import React from 'react'; import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion'; import { FakeDashboard } from '../../_fixtures/Fixtures'; const STRIPS = 12; const W = 160; // 每条宽 12×160 = 1920 const WAVE_START = 20; const STAGGER = 2; // 列号 × 2f const FLIP = 10; // 每条 10f 完成翻换 const WAVE_END = WAVE_START + (STRIPS - 1) * STAGGER + FLIP; // 52 // 条内某页的切片:外层 160 宽裁剪,内层整页 1920 负 margin 对位 const Slice: React.FC<{ x: number; variant: 'A' | 'B' }> = ({ x, variant }) => (
); export const BlindsSlice: React.FC = () => { const frame = useCurrentFrame(); // 摘罩:波完成后条结构全部卸载,B 整页直出 if (frame >= WAVE_END) { return ( ); } const seams: { x: number; opacity: number }[] = []; const strips = Array.from({ length: STRIPS }).map((_, i) => { const x = i * W; const start = WAVE_START + i * STAGGER; const end = start + FLIP; // 未开始:纯 A 切片;已完成:纯 B 切片 if (frame < start) { return (
); } if (frame >= end) { return (
); } // 翻换中:A、B 共用同一进度 p——A 宽 160(1-p) 靠左,B 宽 160p 靠右, // 交接点恒为 x+160(1-p),无露底 const p = interpolate(frame, [start, end], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.cubic), }); // 缝亮线:进出各 2f 线性淡入淡出 const seamOpacity = Math.min( interpolate(frame, [start, start + 2], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }), interpolate(frame, [end - 2, end], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }), ); seams.push({ x: x + W * (1 - p), opacity: seamOpacity }); return (
{/* A:向左缘收缩 */}
{/* B:从右缘展开 */}
); }); return ( {strips} {/* 缝亮线:白底判例——纯提亮不可见,柔光 + 暗描边 + 白核三层 */} {seams.length > 0 && ( {seams.map((s, i) => ( ))} )} ); };