与原生对照:浏览器自带了什么
浏览器原生提供三套动画能力:WAAPI(element.animate())做补间,View Transitions(document.startViewTransition())做 DOM 状态切换的自动过渡,scroll-driven animation(animation-timeline)把动画声明式绑到滚动。
1 · 四种做法并排
前两块的按钮完全对称:play / pause / reverse / seek 中点。差别不在能不能做,而在游标住在哪——WAAPI 的游标在浏览器内部,引擎的游标是一个 JS 变量,可以被手动驱动器逐帧推进。
| 能力 | WAAPI | View Transitions | scroll-driven | @vega/anim |
|---|---|---|---|---|
| seek / scrub | 可 | 不可 | 由滚动隐式驱动 | 可 |
| reverse | 可 | 不可 | 由滚动方向决定 | 可 |
| 确定性测试 | 依赖真实时钟 | 依赖渲染快照 | 依赖滚动环境 | 可,手动驱动器 |
| 跨属性统一 | 逐属性各异 | 仅 crossfade 与 transform | CSS 可动属性 | 统一插值管线 |
| 自定义弹簧与衰减 | 无 | 无 | 无 | spring / decay |
| 浏览器支持 | 广泛 | 较新 | 较新 | 仅需 JS 运行时 |
2 · 各自的定位
WAAPI 是离屏合成的原生补间,性能与播放控制都很好,但跨属性的语义并不统一,也没有弹簧与衰减这类物理模型。View Transitions 解决的是「DOM 从状态 A 切到状态 B 时的自动过渡」,适合路由切换与列表增删,但它不是一个可 seek、可反向的播放器。scroll-driven 用声明式 CSS 把时间轴绑到滚动,零 JS,但时间轴只能来自滚动。
手写引擎不与它们竞争「最省力」,而是补齐可 seek、可确定性测试、跨属性统一插值、不依赖浏览器实现这组属性——把动画当作一条时间到值的纯函数管线,游标由可替换的驱动器推动。
注 · 三项原生 API 在实测 Chrome 151 上均可用:Element.prototype.animate、document.startViewTransition、CSS.supports('animation-timeline: scroll()') 探测皆真。跨浏览器进度以各自的 BCD
为准,且三者的落地节奏并不同步——把它们打包进同一句兼容性断言是这类内容最容易腐坏的写法(核对于 2026-08)。
实践中三者常并用:能用 scroll-driven 声明式表达的就别写 JS;页面与列表的状态切换交给 View Transitions;需要被精确 scrub、需要弹簧与惯性、需要在单元测试里断言每一帧的场景,才用手写引擎。
// 其一: WAAPI — 原生补间, 返回可控的 Animation
const a = el.animate(
[{ transform: 'translateX(0)' }, { transform: 'translateX(240px)' }],
{ duration: 1400, easing: 'ease-in-out', fill: 'both' },
);
a.pause(); a.reverse(); a.currentTime = 700; // 可 seek
// 其二: View Transitions — DOM 状态切换的自动过渡 (feature-detect)
if ('startViewTransition' in document) {
document.startViewTransition(() => reorderDom());
} else {
reorderDom(); // 降级: 直接改 DOM, 无过渡
}
// 其三: @vega/anim — 同样可 pause / reverse / seek, 但游标在 JS 内, 可测试
const anim = new Animation({
keyframes: [0, 1], duration: 1400, ease: 'easeInOut',
onUpdate: (v) => { el.style.transform = `translateX(${v * 240}px)`; },
});
anim.reverse(); anim.seek(700);
/* 其四: scroll-driven — 把动画时间轴绑到滚动容器, 全程无 JS */
.scroller { scroll-timeline-name: --pipe; scroll-timeline-axis: block; }
.fill {
animation: grow linear both;
animation-timeline: --pipe; /* 也可 scroll() / view() */
}
@keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
手写引擎之所以能像 WAAPI 那样 pause、reverse、seek,根因见播放控制;把滚动当作驱动游标的输入、做成可 scrub 的滚动动画见高级交互;View Transitions 自动化掉的那套跨节点 morph,手写版见shared element。