// glitch-displace|噪声置换撕裂
// FakeDashboard A 播到 45f,45–62f 撕裂转场:页面切 16 条水平条带
// (外层 overflow hidden + 内层整页反向 translateY 对位),每条 translateX
// 由 h(条号*31+f*7) 驱动 ±70px 抖动,幅度包络 0→峰值→0(起势 out-cubic、
// 消散线性,冲击判例)。同时叠 2 份整页明暗错位重影(+12px 暗 / -12px 亮反相,
// opacity ≤0.35,灰阶版代替 RGB 分离)。58f 抖动衰减中硬切 variant="B",
// 再抖 4f 至 62f 归位。62f 起摘罩直出 B(条带/重影全部条件卸载),
// 62–135f 真静止 73f ≥ 40f。帧确定:h() 伪随机,无 Math.random。
import React from 'react';
import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion';
import { FakeDashboard, G } from '../../_fixtures/Fixtures';
const STRIPS = 16;
const H = 1080;
const STRIP_H = H / STRIPS; // 67.5
const AMP = 70; // 峰值条带错位(spec 备选加码值,QA 要一眼看到撕裂)
// 库内标准伪随机
const h = (n: number) => {
const s = Math.sin(n * 127.3) * 43758.5453;
return s - Math.floor(s);
};
export const GlitchDisplace: React.FC = () => {
const frame = useCurrentFrame();
const tearing = frame >= 45 && frame < 62;
const variant: 'A' | 'B' = frame >= 58 ? 'B' : 'A';
if (!tearing) {
// 45f 前 A 静置;62f 起 B 摘罩真静止(无 transform / filter / 重影)
return (
);
}
// 幅度包络:45–48f out-cubic 冲起 → 平台 → 56–62f 线性消散(帧驱动,确定性)
const rise = interpolate(frame, [45, 48], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const decay = interpolate(frame, [56, 62], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const env = Math.min(rise, decay);
return (
{/* 底垫一份完整页,防条带间横移露底色缝 */}
{/* 明暗错位重影(灰阶版 RGB 分离):+12px 压暗 / -12px 反相提亮 */}
{/* 16 条水平条带:外层裁切,内层整页反向 translateY 对位 + 逐帧横向抖动 */}
{Array.from({ length: STRIPS }).map((_, i) => {
const dx = (h(i * 31 + frame * 7) * 2 - 1) * AMP * env;
return (
);
})}
);
};