时间重映射:局部慢放与速度曲线
播放头沿一条线性时间轴均匀推进。时间重映射不碰这条轴,而是在它之上再套一个「时间到时间」的映射 :某个子区间被拉伸,对应的运动就慢放;整段先快后慢就是速度曲线。
1 · 一段慢放
两条轨道同时出发、同时到达,差别只在中段一条明显放慢。
2 · 省下的时间必须补回去
重映射后两端仍是 与 :总时长不变,只是把时间重新分配。慢放区省下来的输出量必须在区间外补回,所以两侧的局部速度必然大于 1。
设慢放区宽度为 、倍率为 ,则区间外的补偿速度满足
倍率越低、慢放区越宽,补偿段就越快。
警示 · 这个式子在
时发散:慢放区几乎占满整条轴时,fast 趋于无穷。lab 里把区间止强制不小于起点加 0.05 正是为此——留下 5% 的补偿余量。真做速度曲线时这条约束要写进 UI,否则用户把区间拖满就会得到一段瞬移。
3 · 与缓动同源
缓动是把时间重塑成值进度(时间到进度),重映射是把时间重塑成时间(时间到时间)。两者都是对均匀时间轴再套一层映射,区别只在输出的量纲。
本页的 remap 是分段线性的,所以速度曲线是阶梯状,慢放区的进出是硬切。想要平滑的速度过渡,把分段常速换成一条连续的速度函数、再取它的归一化积分即可——那就是影视软件里的 speed ramp。
// remap: 时间 → 时间。慢放区速度 = rate, 其余补偿到 fast, 使 remap(1) = 1。
function makeRemap(s, e, rate) {
const slow = e - s, fast = (1 - slow * rate) / (1 - slow);
return (u) =>
u <= s ? u * fast
: u >= e ? (u - e) * fast + (s * fast + slow * rate)
: s * fast + (u - s) * rate;
}
const remap = makeRemap(0.35, 0.65, 0.25);
new Animation({
keyframes: [0, 1], duration: 2400,
ease: 'linear', // 取均匀线性播放头 u
repeat: Infinity,
onUpdate: (u) => {
const u2 = remap(u); // 再套一层 时间→时间
ball.style.transform = `translateX(${u2 * maxX}px)`;
},
});