// B 式 穿暗场直航(dark-tunnel)——相机顺运动方向推出前景 → 纯暗场滑行几帧 → // 后景从景深迎面放大而来,一条 take 不切。高能量→高能量的场景跳转主力转场。 // 参考实现(真实纹理):A 景 = projects-full 全满项目板(前镜收尾段), // 推出 10f 相机向右(前景在屏幕空间向左滑出画);暗场段保留微渐变尘点让 // "still moving" 可感,不纯黑死场;B 景 = wbr-full 周报页从 scale ~0.6 + blur // 起步、10f 放大收焦入场。出与入同向(相机向右推出 → B 景从右缘迎来、 // 屏幕空间同样向左),速度曲线连续。 // 节拍:0–26 A hold → 26–36 推出(前景左移出画,暗场 32f 起淡入压住尾巴, // 不留纯黑死帧)→ 40–42 暗场满 → 42–52 B 放大入场 → 52–120 B hold(真静止)。 import { AbsoluteFill, Img, interpolate, staticFile, useCurrentFrame, Easing } from 'remotion'; export const DARKTUNNEL_DUR = 120; // 两景都以 cards 网格区为观察窗;A 景顶对 y=180,B 景(wbr)顶对 y=0 const A_VIEW_Y = -180; const A_W = 1920; const Scene: React.FC = () => { const frame = useCurrentFrame(); // A 景推出:26→36,相机向右推 = 前景在屏幕空间向左平移出画。 // 屏幕空间方向必须与 B 景一致(都向左):之前写成 0→+1920(内容向右) // 与 B 景 +960→0(向左)对撞,读作两条片子硬拼。 const pushX = interpolate(frame, [26, 36], [0, -A_W], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.bezier(0.45, 0, 0.2, 1), }); // 暗场:32→40 从 0→1(进入暗场),42→50 1→0(让 B 景迎入可见)。 // 淡入必须压着 A 段推出的尾巴(f34–35 A 景已基本出画)——从 f36 才起 // 会先漏 2f 纯黑死帧;不纯黑——保留微渐变 + 尘点,让"still moving"可感 const tunnelVis = interpolate(frame, [32, 40, 42, 50], [0, 1, 1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.bezier(0.4, 0, 0.6, 1), }); // B 景迎面放大入场:42→52,从 scale 0.6 + blur 起步收焦,同时沿 // 与 A 相同的运动轴(X 向右)继续移动——出与入同向、速度连续, // 读作"穿过暗场直航"而不是两景各自动自己的。 const bIn = interpolate(frame, [42, 52], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.bezier(0.3, 0, 0.2, 1), }); const bScale = 0.6 + 0.4 * bIn; const bBlur = (1 - bIn) * 12; // 从右缘滑入(+960→0),屏幕空间向左——与 A 段前景向左出画同向, // 相机就是一路向右的一条 take。 const bShiftX = (1 - bIn) * 960; // 尘点:确定性 index 派生(渲染必须可复现)。相机一路向右, // 世界固定的尘点在屏幕空间向左掠过——漂移方向必须与两景同向 const DUST = Array.from({ length: 4 }, (_, i) => ({ x: 780 + i * 320, y: 300 + ((i * 137) % 480), drift: 6 + (i % 3) * 4, // px/frame,缓慢漂移表示"still moving" })); return ( {/* A 景(推出段) */} {frame < 42 ? (
) : null} {/* 暗场:微渐变 + 尘点,非纯黑死场 */} {frame >= 32 && frame < 50 ? (
{DUST.map((d, i) => (
))} ) : null} {/* B 景:沿 A 的运动轴继续向右滑入(自左缘)+ 迎面放大 + 收焦 */} {frame >= 42 ? (
) : null} ); }; export const DarkTunnelTransition: React.FC = () => ( );