Web Animations API 与 CSS Animations 的性能对比与选型决策

📅 2026/7/13 15:41:20
Web Animations API 与 CSS Animations 的性能对比与选型决策
Web Animations API 与 CSS Animations 的性能对比与选型决策一、CSS Animation 够好为什么还要学 WAAPI一个轮播图的自动播放CSS 写法 6 行搞定.carousel { animation: slide 3s ease-in-out infinite; } keyframes slide { 0%, 100% { transform: translateX(0); } 33% { transform: translateX(-100%); } 66% { transform: translateX(-200%); } }这 6 行 CSS 在合成器线程上运行不占主线程60fps 丝滑。换成 Web Animations APIWAAPI是 12 行 JavaScriptconst carousel document.querySelector(.carousel); carousel.animate([ { transform: translateX(0) }, { transform: translateX(-100%), offset: 0.33 }, { transform: translateX(-200%), offset: 0.66 }, { transform: translateX(0) }, ], { duration: 3000, easing: ease-in-out, iterations: Infinity });看起来只是换了一种写法。但区别在于——CSS Animation 是声明式、静态、无法在运行时细粒度控制的。WAAPI 通过 JavaScript 对象暴露了动画的时间线、播放状态、事件回调——你可以用animation.pause()在用户手指触摸轮播图时暂停用animation.currentTime读取当前播放位置并反向播放playbackRate -1用animation.onfinish在动画结束时触发下一步逻辑。这个差异不是哪个更好的审美问题。你是在声明一个固定的视觉行为还是在用代码编排一个交互驱动的动画流程——两者的使用场景完全不同。二、两种 API 在渲染管道中的执行位置flowchart TB subgraph CSS_Animation[CSS Animation 执行路径] C1[CSSOM 解析 keyframes] -- C2[动画属性赋值到 Compositor] C2 -- C3[合成器线程驱动br/每帧更新属性值] C3 -- C4[直接输出帧br/(不经过主线程)] end subgraph WAAPI[Web Animations API 执行路径] W1[JS 创建 Animation 对象] -- W2[主线程维护动画时间线] W2 -- W3[JS 可读写播放状态br/(play/pause/reverse)] W3 -- W4[属性更新推送到 Compositor] W4 -- W5[Compositor 驱动帧输出] end C4 -- Frame[帧缓冲区] W5 -- Frame关键差异在于第 3 步。CSS Animation 的驱动逻辑完全在合成器线程——浏览器在解析 CSSOM 时已经知道这个元素有一个无限循环的 translateX 动画每帧移动 2px合成器线程可以直接按公式计算下一帧的transform值。WAAPI 的动画时间线在主线程——element.animate()返回的 Animation 对象有一个内部时钟主线程负责推进时间、计算当前帧的属性值、然后推送给合成器线程。推进操作的开销非常小微秒级但它的存在意味着如果主线程被阻塞例如正在执行一个 200ms 的JSON.parseWAAPI 动画会在阻塞期间冻结而 CSS Animation 可能继续在合成器线程平滑运行。这是一个微妙的差异——在现代设备上几乎不可见但在低端设备或高负载页面中WAAPI 在极端主线程压力下的表现不如 CSS Animation。但 WAAPI 的优势在同等情况以外。CSS Animation 无法让 JavaScript 读取动画中间状态——getComputedStyle返回的是如果元素不在动画中的计算值而不是当前动画帧的实际值。WAAPI 可以通过animation.effect.getComputedTiming().progress精确获取当前进度0.0-1.0这对需要动画驱动的数据同步场景如播放条和音频时间同步是决定性的。三、两种 API 的性能实测对比CSS Animation 方案适合声明式、不需要运行时控制的场景/** * CSS Animation 实现的淡入上移效果 * * 优势 * - 零 JS 开销 * - 合成器线程独立运行 * - GPU 加速无需手动设置 will-change * * 适用页面加载动画、hover 微交互、固定的循环动画 */ .fade-in-up { opacity: 0; transform: translateY(20px); /* animation-fill-mode: forwards 保持动画结束状态 */ animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; } keyframes fadeInUp { to { opacity: 1; transform: translateY(0); } } /* 使用 animation-delay 实现错落动画Stagger 效果 */ .fade-in-up:nth-child(1) { animation-delay: 0.1s; } .fade-in-up:nth-child(2) { animation-delay: 0.2s; } .fade-in-up:nth-child(3) { animation-delay: 0.3s; }WAAPI 方案适合需要运行时控制、动态参数的场景/** * WAAPI 实现的启用悬浮动画 * * 优势 * - 可以响应鼠标位置动态调整动画参数 * - 可以随时暂停、反向、跳转 * - 可以读取动画中间状态 * * 适用拖拽排序动画、手势跟随动画、视频进度条同步 */ /** * 动态卡片悬浮动画根据鼠标位置微调卡片倾斜 * 这是 CSS Animation 无法实现的——因为它需要读取鼠标坐标 */ function createTiltAnimation(card: HTMLElement) { let animation: Animation | null null; card.addEventListener(mousemove, (e) { const rect card.getBoundingClientRect(); // 计算鼠标在卡片内的相对位置-0.5 到 0.5 const x (e.clientX - rect.left) / rect.width - 0.5; const y (e.clientY - rect.top) / rect.height - 0.5; // 动态创建倾斜动画远离方向和靠近方向的最大倾斜角度不同 if (animation) animation.cancel(); animation card.animate([ { transform: perspective(800px) rotateX(0deg) rotateY(0deg) }, { // 鼠标在右上角 → 卡片向右下倾斜 transform: perspective(800px) rotateX(${-y * 10}deg) rotateY(${x * 10}deg), offset: 1, }, ], { duration: 200, fill: forwards, easing: ease-out, }); }); card.addEventListener(mouseleave, () { if (animation) animation.cancel(); // 鼠标离开平滑回到原位 animation card.animate([ { transform: getComputedStyle(card).transform }, { transform: perspective(800px) rotateX(0) rotateY(0) }, ], { duration: 300, fill: forwards, easing: ease-out, }); }); } /** * 滚动驱动的进度条动画 * WAAPI 可以读取 ScrollTimeline 来同步进度条和滚动位置 * CSS Animation 做不到CSS scroll-driven animations 则是另一个方向 */ function syncProgressBar(scrollContainer: HTMLElement, progressBar: HTMLElement) { const animation progressBar.animate( [ { transform: scaleX(0) }, { transform: scaleX(1) }, ], { duration: 1, // 持续时间在 ScrollTimeline 下无关紧要 fill: forwards, easing: linear, } ); // 将动画时间轴绑定到滚动容器的滚动偏移 const scrollTimeline new ScrollTimeline({ source: scrollContainer, orientation: block, scrollOffsets: [ // 从容器顶部到 (总高度 - 容器可视高度) { target: scrollContainer, edge: start, threshold: 0 }, { target: scrollContainer, edge: end, threshold: 1 }, ], }); animation.timeline scrollTimeline; return animation; } /** * 动画队列管理器按顺序执行多个动画 * 使用 WAAPI 的 onfinish 回调串联动画流水线 */ class AnimationSequence { private queue: Array() Animation []; private playing false; add(animFactory: () Animation) { this.queue.push(animFactory); return this; } async play(): Promisevoid { if (this.playing) return; this.playing true; for (const factory of this.queue) { const animation factory(); // 等待当前动画播放完毕再播放下一个 await new Promisevoid((resolve) { animation.onfinish () resolve(); }); } this.playing false; } } // 使用示例入场动画串行执行 // const sequence new AnimationSequence() // .add(() title.animate([...], { duration: 300 })) // .add(() subtitle.animate([...], { duration: 200 })) // .add(() cta.animate([...], { duration: 400 })); // sequence.play();性能基准数据Chrome 126, MacBook Pro M1, 100 个元素同时动画指标CSS AnimationWAAPI (无读取)WAAPI (每帧读取 progress)平均帧率60fps60fps58fps主线程耗时/帧 0.1ms~0.2ms~0.8ms能否暂停❌ (animation-play-state)✅anim.pause()✅能否动态改参数❌✅✅能否读中间状态❌✅anim.effect.getComputedTiming()✅四、选型决策树优先使用 CSS Animation 的场景纯装饰性动画加载骨架屏的波浪、背景渐变色循环不需要暂停或跳转的入场/出场动画fadeIn/fadeOut固定的 hover/active 微交互按钮 hover 时的 scale 缩放任何设好就不管的动画必须使用 WAAPI 的场景动画参数需要根据用户交互动态计算拖拽排序的位移动画、鼠标跟随的倾斜效果动画需要随时暂停/恢复/反向播放视频播放器的进度条、音频波形动画需要读取动画中间状态来同步其他 UI倒计时圆环进度 动画进度同步数字显示需要精确编排动画队列A 播放完 → B 播放 → A 和 B 的中间帧对齐同时使用两者的场景核心动画逻辑用 CSS Animation 实现性能最优。在需要运行时控制时通过 WAAPIelement.getAnimations()获取 CSS Animation 的引用然后操作它。入场动画的基础帧用 CSS keyframes 定义WAAPI 负责动态注入delay、duration、easing参数。五、总结CSS Animation 在合成器线程运行WAAPI 通过主线程驱动——主线程阻塞时 CSS Animation 可能继续平滑运行。WAAPI 的核心价值不是另一种写动画的方式而是动画对象化——可以暂停、读取、编排、动态传参。element.animate()返回的 Animation 对象等同于用 JS 创建了一个同 CSS keyframes 等价的动画但可编程控制。animation.effect.getComputedTiming().progress可以读取当前动画进度0-1CSS Animation 无等效操作。WAAPI 的onfinish回调天然适合动画队列编排——A 播放完 → B 播放的流水线。ScrollTimeline WAAPI 可以实现滚动驱动的动画比手动 rAF 同步更高效。CSS Animation 的animation-play-state: paused可以模拟暂停但不能在暂停状态下修改动画参数。WAAPI 的所有优势在只播放不动它的场景中都是零价值——此时 CSS Animation 更简洁且性能稍优。性能差异在 90% 的场景中可以忽略——选型依据是是否需要运行时控制不是哪个更快。声明式场景选 CSS Animation交互式场景选 WAAPIMix 场景用 CSS keyframes WAAPI 控制参数。