// ===== demos/rhythm/quad-split-parallel-scenes/QuadSplitParallelScenes.tsx ===== // quad-split-parallel-scenes — Quad Split 四宫并行蒙太奇(motion-lab 定稿转原生 Remotion) // 手法卡:画面硬切成 2×2 四宫格,四个象限并行跑各自独立的微场景(格内内容可任意替换, // 此处仅为示例):TL 迷你浏览器打字 + 标签堆积 + 慢推,TR mono 打字 + whip 急推, // BL 三词逐个弹入,BR pill 滑入 → 光标贝塞尔飞行点击 → 卡片弹出。 // 关键节拍互相错开 3-6 帧、全程无转场,靠并行密度制造信息轰炸。 // 设计坐标 480×270(DesignStage 等比放大),参数以此坐标系标定。 import React from 'react'; import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion'; export const QUAD_SPLIT_PARALLEL_SCENES_DURATION = 63; // 2100ms @30fps const ACCENT = '#7c5cff'; const ACCENT_SOFT = '#c9bcff'; const F = 'system-ui,-apple-system,sans-serif'; // 四宫格底色走中性灰阶(深浅交替保证格线可读),需要主题色时换成项目色板 const BGS = ['#c3c6cc', '#f2f1ef', '#e7e6e3', '#b8bcc3']; const TRAFFIC = ['#ff5f57', '#febc2e', '#28c840']; const TABNAMES = ['Tab One', 'Tab Two', 'Tab Three', 'Tab Four', 'Tab Five', 'Tab Six']; const TXT1 = 'Placeholder headline text'; const TXT2 = 'and a second line of copy'; const WORDS = ['One', 'clear', 'message']; // 二次贝塞尔取点(BR 象限光标飞行路径,坐标单位 %) const qBez = (a: number[], b: number[], c: number[], t: number): [number, number] => { const u = 1 - t; return [ u * u * a[0] + 2 * u * t * b[0] + t * t * c[0], u * u * a[1] + 2 * u * t * b[1] + t * t * c[1], ]; }; // TL —— 迷你浏览器:逐字符打字 + tab outBack 弹入 + 全程 inQuad 慢推 const QuadTL: React.FC<{ t: number; frame: number }> = ({ t, frame }) => { const n1 = Math.floor(seg(t, 0.02, 0.95) * TXT1.length); return (
{TRAFFIC.map((c) => ( ))}
{TABNAMES.map((n, i) => { const k = seg(t, 0.08 + i * 0.13, 0.08 + i * 0.13 + 0.09, E.outBack); return (
{n}
); })}
◆ {TXT1.slice(0, n1)} {/* 光标按帧闪烁:16 帧一个周期 */}
); }; // TR —— mono 打字 + whip 急推(错拍:0.42-0.54,推近时带运动模糊) const QuadTR: React.FC<{ t: number; frame: number }> = ({ t, frame }) => { const n2 = Math.floor(seg(t, 0.06, 0.9) * TXT2.length); const zip = seg(t, 0.42, 0.54, E.inOutCubic); return (
{TRAFFIC.map((c) => ( ))}
✦ Section label ›
{TXT2.slice(0, n2)} {/* 光标错拍闪烁:与 TL 相位差 5 帧、周期 14 帧 */} _
); }; // BL —— 三词逐个 outBack 弹入(0.24 / 0.46 / 0.56,与其他象限错开) const QuadBL: React.FC<{ t: number }> = ({ t }) => (
{WORDS.map((w, i) => { const k = seg(t, [0.24, 0.46, 0.56][i], [0.24, 0.46, 0.56][i] + 0.1, E.outBack); return ( {w} ); })}
); // BR —— 五步交互:pill 滑入 → 光标贝塞尔飞行 → 点击缩放 → 打字回复 → 卡片弹出 const QuadBR: React.FC<{ t: number }> = ({ t }) => { const slide = seg(t, 0.12, 0.3, E.outBack); const m1 = seg(t, 0.3, 0.42, E.inOutCubic); const m2 = seg(t, 0.62, 0.74, E.inOutCubic); // 光标路径:先飞向 pill,再二段飞向发送键 const p = m2 > 0 ? qBez([44, 66], [66, 52], [82, 68], m2) : qBez([88, 30], [50, 40], [44, 66], m1); const c1 = seg(t, 0.42, 0.47); const c2 = seg(t, 0.74, 0.79); const n4 = Math.floor(seg(t, 0.46, 0.62) * 9); const pop = seg(t, 0.8, 0.88, E.outBack); return (
{/* 评论卡片:点击发送后弹出 */}
You · just now
All good!
{/* 输入 pill:从右侧滑入 */}
00:00 {t < 0.44 ? 'Leave your comment...' : 'All good!'.slice(0, n4)} = 9 ? ACCENT : ACCENT_SOFT }}>➤
{/* 光标圆点:两段点击各缩一次 */}
); }; export const QuadSplitParallelScenes: React.FC = () => { const t = useT(); const frame = Math.floor(t * 63); // dur 2100ms @30fps const scenes = [ , , , , ]; return ( {scenes.map((scene, i) => (
> 1) * 50}%`, width: '50%', height: '50%', overflow: 'hidden', background: BGS[i], }} > {scene}
))}
); };