AnimationWorklet:把动画逻辑搬下主线程
本引擎的 tick 跑在主线程:主线程一忙,rAF 就被推迟,动画掉帧。合成层讲过 transform 与 opacity 的声明式动画能下到合成器线程、不受主线程阻塞,但那只能表达固定关键帧。
AnimationWorklet 是平台给「让自定义 JS 动画逻辑也跑在合成器线程」的答案:写一个 animate(currentTime, effect) 函数注册成 animator,它在 worklet 线程逐帧运行。
1 · 主线程阻塞时的三条轨
三条轨同时运动:rAF 由主线程逐帧驱动,CSS 那条是声明式 @keyframes 跑在合成器,第三条若支持则由 worklet 线程驱动。
掉帧的根因是「动画逻辑在哪条线程跑」。rAF 与引擎的 tick 都和业务 JS 抢同一条线程;transform 与 opacity 的声明式动画被浏览器下放到合成器线程,所以主线程死循环时它照动。
2 · 一个已经停摆的答案
AnimationWorklet 至今是实验特性,规范长期停滞。Chromium 之外的浏览器不支持,连 Chromium 默认构建也不再暴露它。
警示 · 实测 Chrome 151 的 'animationWorklet' in CSS 为 false,而同属 Houdini 的 paintWorklet 为 true——同一套 Houdini 提案里,绘制那半边落了地,动画这半边没有(核对于 2026-08)。要跑通本页第三条轨需自行开启实验标志;未启用时它降级为一条
CSS compositor 动画占位,与第二条同源。
它的限制也很实在:worklet 线程读不到 DOM,输入受限于时间与滚动等少数几个信号,输出只能是 effect.localTime——即「驱动一个预先声明好的 KeyframeEffect 走到第几毫秒」。能表达的自定义逻辑因此只是「时间到时间」的映射,与时间重映射同构。
所以现实中,绝大多数「不掉帧」的工程手段仍是只动合成属性加单 rAF 与读写分离。
// worklet 模块 (跑在 worklet 线程, 不能碰 DOM): 自定义 currentTime → 进度
registerAnimator('tri-mover', class {
animate(currentTime, effect) {
const phase = (currentTime % 2000) / 2000;
const tri = phase < 0.5 ? phase * 2 : 2 - phase * 2; // 自定义逻辑
effect.localTime = tri * 2000; // 驱动一个 KeyframeEffect
}
});
// 主线程: 注册模块, 把 effect 交给 worklet animation
await CSS.animationWorklet.addModule(workletURL);
const effect = new KeyframeEffect(box,
[{ transform: 'translateX(0)' }, { transform: 'translateX(230px)' }],
{ duration: 2000, fill: 'both' });
new WorkletAnimation('tri-mover', effect, document.timeline).play();
// 此后即便主线程死循环, 这段动画仍在 worklet 线程逐帧运行
为什么 rAF 必须按 delta-time 累加才帧率无关见驱动器与帧;哪些属性能下合成器见合成层;一帧内如何批量读写见调度。