Animation.reverse() 少实现的两个规范行为,Firefox 157 补上了——playbackRate=0 从空操作变成真倒放,滚动动画反向不再跑出滚动范围
写播放器控制条的人都可能踩过这个坑:动画暂停着(playbackRate 已经被你设成 0),再调 reverse() 想让它倒着播——在 Firefox 157 之前,这一行代码没有任何反应,动画既不动也不报错。
这不是你的调用顺序有问题,是 Firefox 的 reverse() 和 playbackRate 少实现了两处 Web Animations 规范行为。Firefox 157(2026-09-29 发布)在 Bug 2046973 里把两处一次补齐了。
行为一:playbackRate=0 时调 reverse(),以前是空操作
Before(Firefox 157 之前):
// 典型场景:进度条动画,点暂停把播放速率归零
const anim = progressEl.animate(
[{ transform: 'scaleX(0)' }, { transform: 'scaleX(1)' }],
{ duration: 2000, fill: 'forwards' }
);
anim.playbackRate = 0; // 暂停在原地
anim.pause();
// 用户点「倒放」按钮
anim.reverse(); // ❌ 什么都没发生:不开始播放,startTime/currentTime 都不更新
After(Firefox 157):
同样的调用现在会真正播放动画——浏览器更新 startTime 和 currentTime,而 playbackRate 仍然保持 0。这正是规范写的行为:reverse() 负责把播放方向翻过去并推进时间,播放速率归不归零是另一件事。WPT 测试用例的断言很直白:reverse() should preserve playbackRate if the playbackRate == 0,同时 reverse() should not affect the currentTime if the playbackRate == 0(速率为 0 时 currentTime 不因 reverse 本身跳变)。
行为二:滚动驱动动画反向,以前会跑出滚动范围
scroll-driven animation(比如随滚动增长的阅读进度条)把 playbackRate 在正负之间切换时:
Before: startTime 原地不动。这个算法只对时间驱动的 DocumentTimeline 成立——滚动时间轴下,反向后的动画落点直接飘出滚动区间,视觉上表现为进度条突然跳到头或者消失。
After: Firefox 157 会把 startTime 镜像到时间轴的另一端,反向后的动画仍然落在滚动范围内。注意两个前提:动画必须已有 startTime,且 duration 有限。
// 阅读进度条:往下滚正放,往上滚反着回退
const bar = document.querySelector('.progress');
const anim = bar.animate(
[{ transform: 'scaleX(0)' }, { transform: 'scaleX(1)' }],
{ duration: 1, fill: 'forwards' }
);
anim.timeline = new ScrollTimeline({
source: document.scrollingElement,
orientation: 'block',
scrollRange: [0, '100%']
});
// 滚动方向变化时切速率——157 之前这一行会让进度条跑出页面范围
window.addEventListener('scroll', () => {
const down = window.scrollY > lastY;
anim.playbackRate = down ? 1 : -1;
lastY = window.scrollY;
}, { passive: true });
跨浏览器怎么验证
这两处是 Firefox 向 Web Animations 规范对齐的修正,不要假设另外两个引擎行为完全一致。最快的办法是直接跑 WPT 里的 web-animations/timing-model/animations/reversing-an-animation.html 和 scroll-animations/scroll-timelines/reverse-animation.html 两个用例,三个引擎各跑一遍,断言全部通过即为规范行为。
下一步(今天就能做)
- 升级 Firefox 到 157+,在播放器暂停逻辑里复现一次 reverse() 空操作场景,确认修复生效;
- 全局搜一遍项目里的
reverse()和playbackRate = 0组合调用,凡是「暂停后再倒放」的交互都过一遍; - 有 scroll-driven 动画方向切换的页面,在三引擎各测一次反向落点是否仍在滚动范围内。
评论区
登录后可评论。