Web Animations API 实战:用 JS 精确控制动画(play/pause/.currentTime 与 finished)

#WebAnimationsAPI实战:用JS精确控制动画(play/pause/.currentTime与finished)老实说,我以前是CSS`animation`的死忠粉。什么loading转圈、按钮…

Web Animations API 实战:用 JS 精确控制动画(play/pause/.currentTime 与 finished)

老实说,我以前是 CSS animation 的死忠粉。什么 loading 转圈、按钮 hover、列表入场,通通一个 @keyframes加transition 搞定,写得不亦乐乎,还觉得自己挺懂动画的。直到去年做一个带"可拖拽进度条的播放入场动画"——产品拍着肩膀说:"用户要能中途暂停、往后拖进度、还能 0.5 倍速看慢动作。"我当场破防。

CSS 动画你让它播可以,让它"听指挥"基本抓瞎:暂停得靠 animation-play-state 切 class,定位到某一帧?不好意思没有这个接口;调速?只能重新写一遍 animation-duration,还得把别的相关属性一起改;至于"播到第 500 毫秒那一帧停住",CSS 根本给不了你这个能力。我那天改了七八版 class 组合,页面里 setTimeout 满天飞,最后自己都看不懂哪段逻辑在控制哪个动画,测试同学提 bug 说我"动画状态乱了",我连复现都复现不出来。

后来才知道,浏览器早就给了我们一把能精确操控动画的"手术刀"——Web Animations API(简称 WAAPI)。它不是一个新动画引擎,而是把浏览器底层那套动画时序系统,正儿八经地用 JS 暴露了出来。今天这篇就是把我踩过的坑、以及它到底能精确到什么程度,一次讲透。不堆概念,全是我真实用过的代码。

一、我当初为什么被 CSS 动画卡住

先把话说清楚:我不是来踩一捧一的,CSS 动画很好,它在"声明式、纯装饰、不依赖 JS 状态"的场景里是首选。我踩的坑,本质上是把"需要被程序精确控制的交互型动画"也用 CSS 去硬扛了。

举个最典型的例子。你做一个卡片入场:从下往上滑入、淡入,纯 CSS 三五行搞定,没问题。但一旦需求变成"滑到一半用户点了暂停,过五秒再继续,继续的时候要从暂停那一帧无缝接上",CSS 的 animation-play-state: paused确实能暂停,可你拿不到当前停在哪一帧,也就没法在 UI 上画一个进度条;你想反向回放?CSS 只能靠 animation-direction: reverse重新触发,等于又播了一遍;你想在动画结束那一刻精准地做点什么(比如解锁按钮),虽然能监听animationend,但那是个浏览器事件,和你的异步逻辑整合起来总差口气。

一句话总结我当时的问题:CSS 把"动画"做成了样式,而我要的是把"动画"当成一个有状态、可查询、可指挥的对象。WAAPI 干的就是这件事——它创建的每一个动画,都是一个活生生的 Animation对象,挂在内存里,随你捏。

顺便交代一个背景,免得你怀疑我"早干嘛去了"。我当初之所以死守 CSS,是因为早些年 WAAPI 的兼容性确实是块心病,Safari 拖了很久才跟上,团队里前辈也反复念叨"动画还是写 CSS 稳"。但那是好几年前的事了。现在的现实是,WAAPI 的核心接口在 Chrome、Firefox、Edge 以及较新的 Safari 上都是齐活的,移动端也基本覆盖。所以我现在的态度很明确:纯装饰的动画继续 CSS,但凡需要一点点"程序介入"的,直接上 WAAPI,基本不用担心用户跑不起来。下面进入正题。

二、element.animate():最顺手的那把刀

WAAPI 的入口特别简单:任意 DOM 元素都有 .animate()方法。你喂它两样东西——关键帧数组和时间轴配置——它就还你一个Animation对象。后面所有控制,全作用在这个对象上。

javascript
复制
// 拿到要动的元素const box = document.querySelector('#box');// element.animate(keyframes, options)// 第一参数是关键帧数组(描述"从哪到哪"),第二参数是时间轴配置(描述"怎么播")const anim = box.animate( [ { transform: 'translateX(0px)', opacity: 1 }, // 第 0 帧:初始位置、完全不透明 { transform: 'translateX(300px)', opacity: 0 } // 最后一帧:右移 300px、完全透明 ], { duration: 1000, // 持续 1000 毫秒(注意单位是毫秒,不是秒) easing: 'ease-in-out', // 缓动曲线,和 CSS 的 timing-function 一个意思 iterations: Infinity, // 循环次数:Infinity 无限循环,播一次就写 1 fill: 'forwards' // 播完后停在最后一帧,而不是瞬间弹回初始状态 });// 重点:animate() 返回的 anim 是个 Animation 实例// 你后面要的暂停、定位、调速,全靠它,不用再去 querySelectorconsole.log(anim.playState); // 输出"running",说明正在播

关键帧数组的写法很灵活。上面是"起始帧 + 结束帧"两帧,中间靠浏览器插值;你也可以写多帧,比如 { transform: 'scale(1)' }→{ transform: 'scale(1.2)' }→{ transform: 'scale(1)' } 做个呼吸效果。每个关键帧里能写任意 CSS 属性(transform、opacity、backgroundColor等等),只要它是可动画的属性就行。

时间轴配置里最该记住的几个字段:duration(时长)、delay(延迟开始)、easing(缓动)、iterations(次数)、direction(方向,normal/reverse/alternate等)、fill(填充模式,决定播前播后元素长啥样)。这些值和 CSS animation-* 系列几乎一一对应,老 CSS 玩家零成本上手。

再多嘴一句关键帧数组的写法,这是很多人第一次用会懵的地方。除了上面"首尾两帧"的写法,你还可以给每一帧指定offset(0 到 1 的相对位置)来控制它出现在动画时间轴的哪个比例上,也能给单帧单独写 easing。比如[{opacity:0, offset:0}, {opacity:1, offset:0.2}, {opacity:0, offset:1}] 表达的就是"前百分之二十快速淡入、后百分之八十慢慢淡出"的节奏。另外属性可以拆分到不同帧、只写发生变化的那一个,没写的帧浏览器会自动从相邻帧插值补全。把这些组合好了,纯靠关键帧就能做出相当细腻的节奏感,不一定非要靠 JS 去逐帧算。

三、play / pause / reverse / cancel / finish:五个开关

拿到anim之后,最常用的一批方法就是播放控制。这五个方法我几乎每天都要用,先一次性说清楚,免得你和我当初一样傻傻分不清cancel和pause。

javascript
复制
// ---- 播放控制的五个开关 ----anim.play(); // 继续播放(从当前位置接着走,不是从头)anim.pause(); // 暂停(冻结在当前帧,currentTime 不变)anim.reverse(); // 反向播放(等价于把 playbackRate 取负,从尾往头播)anim.cancel(); // 取消:动画彻底消失,元素瞬间回到"没播过"的状态,currentTime 变 nullanim.finish(); // 立即跳到最后一帧并触发 finish(不管现在播到哪)// 想知道现在到底什么状态?读 playState,别再自己维护一堆 flag// 取值只有四种:'idle'(还没开始) | 'running'(播放中) | 'paused'(暂停) | 'finished'(已结束)if (anim.playState === 'running') { anim.pause(); // 正在播就先停一下,比如用户切到后台标签页时}

这里有两个我当初踩过的坑,务必划重点:

坑一:pause()和cancel()完全不是一回事。pause()是"冻住",元素停在当前帧,你随时play()能接上;cancel()是"销毁",元素直接回到动画之前的状态,进度清零,而且——它会让finished 这个 Promise 走 reject(后面第七节会讲,这是个坑中坑)。我的血泪教训是:凡是"用户暂时不想看但可以恢复"的,用pause;凡是"这个动画的使命彻底结束、不打算再播"的,才用cancel,用完记得把引用清掉,别留着占内存。

坑二:reverse()不是"倒带重播",而是"反向继续"。它把播放方向翻过来,从当前currentTime 往 0 走。所以如果你先 finish()再到头了,再reverse()它会从尾往回播,符合直觉;但如果你想"从头反向",得先把currentTime设成duration再reverse(),或者直接playbackRate = -1。这个细节我调一个"返回按钮动画"时纠结了半天才搞明白。

还有个小细节值得单独拎出来说:playState里的'idle'状态最容易被忽略。一个刚animate()创建出来、还没真正起播的动画就是'idle';你调了play()之后才会变成'running'。这个区分在写"防重复播放"逻辑时特别有用——比如用户手快疯狂点按钮,你可以先判断if (anim.playState === 'idle' || anim.playState === 'finished') anim.play();,避免重复触发导致动画跳回开头重播。我早年就被"连点让动画从头重来"折磨过,加上这个判断之后就稳了。

四、currentTime:把动画当成进度条来拖

这是 WAAPI 最让我惊艳的能力,也是当年 CSS 死活给不了的——你可以像拖视频进度条一样,把动画定位到任意一帧。

currentTime的单位是毫秒(一个浮点数),它代表动画"播放头"当前所在的位置。读它知道播到哪了,写它直接跳帧,绝不拖泥带水。

javascript
复制
// ---- currentTime:动画的"播放头"位置,单位毫秒 ----anim.currentTime = 500; // 直接把动画拖到第 500 毫秒那一帧console.log(anim.currentTime); // 500// 实战:做一个进度滑块,拖动就定位动画const slider = document.querySelector('#slider');slider.addEventListener('input', (e) => { // 滑块值 0~100,映射成动画 0~总时长 const ratio = e.target.value / 100; // effect.getComputedTiming().duration 拿到的是单轮迭代时长(毫秒) const total = anim.effect.getComputedTiming().duration; anim.currentTime = ratio * total;});// 想回到开头但不重播?直接归零,比 cancel 温柔多了anim.currentTime = 0;

我第一次用currentTime 做拖拽进度条的时候,手都在抖——原来不用任何 hack,三行代码就实现了我当初用 CSS 折腾一整周都没搞定的东西。而且它是实时的,你边拖边看到元素跟着动,体验顺滑得不像话。

提醒一个点:currentTime和playState是两个维度。你设了currentTime但不play(),动画是停在那一帧的(相当于paused状态定位)。如果你希望定位后继续播,记得后面跟一句anim.play()。

还有一个特别实用的衍生用法:配合currentTime我能轻松实现"从某一刻重播"。比如一个十秒的产品演示动画,用户说"刚才那段没看清,从六秒处再放一遍",我只要anim.currentTime = 6000; anim.play();就完事,根本不用重新animate 一套。这种把时间直接当参数传的自由度,是纯 CSS 时代想都不敢想的,也是我后来最爱用 WAAPI 的理由之一。

五、playbackRate:调速不靠重写 duration

又一个 CSS 没法优雅解决的问题:用户想看慢动作。CSS 里你只能改 animation-duration,但一改duration,animation-delay、iteration 的体感全变了,而且正在播的动画会"跳"。WAAPI 给了 playbackRate,顾名思义就是"播放速率",改它就行,别的都不动。

javascript
复制
// ---- playbackRate:调速,而不是改 duration ----anim.playbackRate = 2; // 2 倍速anim.playbackRate = 0.5; // 0.5 倍慢动作,做教学演示神器anim.playbackRate = -1; // 负数 = 反向播放,和 reverse() 一个效果但更可控// 关键认知:playbackRate 改的是"速度",总时长 duration 不变// 也就是说慢放时,动画播完需要的"真实墙钟时间"变长了// 但 currentTime 的上限还是 duration,不会变成两倍// 实战:做个倍速按钮组document.querySelectorAll('[data-speed]').forEach(btn => { btn.addEventListener('click', () => { anim.playbackRate = Number(btn.dataset.speed); });});

我做一个"代码演示自动播放"功能时就靠它:默认 1 倍速,用户喊"太快了看不清",我 playbackRate = 0.3立刻变慢动作,丝滑到不行。另外playbackRate配合负数还能优雅做"反向",比reverse()更适合需要动态切换方向的场景,因为你随时能把它从-1切回1。

六、updateTiming:运行时动态改时间轴

有时候需求是"播到一半,用户说这个动画太长了,缩短到 1.5 秒"。CSS 做不到运行时改时长还不断播,WAAPI 的 updateTiming()专门干这个——它改的是动画的"时间轴配置",而且不会打断当前播放,画面平滑过渡。

javascript
复制
// ---- updateTiming:运行时动态改时间轴参数 ----// 比如用户吐槽太快,拉长到 3 秒并改成线性anim.updateTiming({ duration: 3000, // 改时长 easing: 'linear' // 改缓动曲线});// 也可以只改其中一项,没提到的保持原样anim.updateTiming({ iterations: 3 }); // 让它再播三圈// updateTiming 改的是"时序",和改 currentTime 不一样:// currentTime 是移动播放头,updateTiming 是改整条时间轴的长度/形状// 想看当前生效的全部时间参数?读 effect 的 getComputedTiming()const timing = anim.effect.getComputedTiming();console.log(timing.duration, timing.easing, timing.iterations, timing.delay);

这里有个组合技值得记:updateTiming改duration,配合currentTime 实时定位,你就能做一个"用户可以拖拽调时长"的动画编辑器——我后面综合实战里会用到类似的思路。说白了,WAAPI 把动画从一个"黑盒样式"变成了一个"白盒对象",你能改的维度比 CSS 多太多了。

七、finished Promise 与 onfinish:动画结束怎么接

动画播完你要干点啥?比如点亮"已完成"按钮、跳转下一页、解锁交互。WAAPI 给了两种姿势,我都用,看场景选。

javascript
复制
// ---- 动画结束的两种监听姿势 ----// 姿势一:回调属性(简单直接,适合一个动画对应一个收尾动作)anim.onfinish = () => { console.log('动画播完了,可以点亮"完成"按钮');};anim.oncancel = () => { console.log('动画被 cancel 了,记得把 UI 状态清干净');};// 姿势二:finished Promise(适合 async/await 链式,能 await 等待)anim.finished .then(() => console.log('正常结束')) .catch(() => console.log('被 cancel / 出错,Promise 会走 reject'));// 划重点:cancel() 会让 finished Promise 走 reject 分支,别当成 bug 去捕获空了// 实战:等两段动画依次播完再做下一步(这就是 WAAPI 版的"动画序列")async function playSequence() { const a = box.animate([{opacity: 0}, {opacity: 1}], {duration: 500}); await a.finished; // 等淡入结束 const b = box.animate( [{transform: 'translateX(0)'}, {transform: 'translateX(200px)'}], {duration: 800} ); await b.finished; // 再等位移结束 console.log('两段动画依次播完,可以进下一步');}

关于"动画序列"我多说一句:网上有些文章会写document.timeline.createSequence()或者new AnimationGroup() 之类的 API,那不是 WAAPI 标准里真实存在的东西,我特意没用,免得你抄去跑不起来。finished 这个 Promise 才是标准、靠谱的做法——你想串几个动画,就 await几个finished,比任何"组/序列"对象都直观。

另外,除了finished,WAAPI 还有个 ready Promise(动画"可以开始播放"时 resolve,通常在你 play()之后、真正起播之前),以及oncancel/onremove 回调。日常 90% 场景用 finished+onfinish就够了。

具体选哪个姿势?我的经验是:单个动画、收尾动作简单,用onfinish回调最直白,写在哪都能看懂;如果收尾要参与async异步流程、或者要等好几个动画依次串完再统一处理,就用finished 这个 Promise 去 await,流程清晰不易乱。两者并不互斥,同一个动画既挂onfinish又await finished完全没问题,看你怎么组织代码更舒服。

八、getAnimations():把 CSS @keyframes 也抓来管

前面讲的都是用element.animate() 创建的动画。但现实项目里,页面上大量动画其实是写在 CSS 里的 @keyframes。你可能会想:那部分我是不是管不了?

错。WAAPI 最妙的一招是 getAnimations()——它能把某个元素(甚至整个文档)上当前正在跑的所有动画都抓出来,不管是 WAAPI 创建的,还是 CSS @keyframes驱动的,一视同仁地还你Animation对象数组。拿到之后,前面学的pause、currentTime、playbackRate全部能用上。

javascript
复制
// ---- getAnimations:把页面里的 CSS @keyframes 动画也抓出来控制 ----// 假设 HTML 里有个元素是靠 CSS 驱动的:// .spinner { animation: spin 1s linear infinite; }// 以前你没法用 JS 精确控制它,现在可以:const spinner = document.querySelector('.spinner');const cssAnims = spinner.getAnimations(); // 返回该元素上所有动画(含 CSS 动画与过渡)const spin = cssAnims[0]; // 拿到那个 spin 动画的 Animation 对象// 之后和 WAAPI 创建的动画一视同仁地控制spin.pause(); // 暂停 CSS 动画spin.currentTime = 250; // 定位到半圈的位置spin.playbackRate = 3; // CSS 动画也能 3 倍速spin.updateTiming({ iterations: 1 }); // 让它只转一圈就停// 整页通杀:document.getAnimations() 能拿到全页所有动画// 做"全局暂停"特别爽,比如用户切到后台时一键冻结所有动效// document.getAnimations().forEach(a => a.pause());

我在一个后台系统里就用这招做了"降低动效"的无障碍开关:监听系统prefers-reduced-motion,一旦开启,就document.getAnimations().forEach(a => a.pause()),不管是 CSS 的还是 JS 的动画,统统停下来,用户体验直接拉满。

讲到这里你大概能感觉到,CSS 动画和 WAAPI 其实不是"二选一"的关系。它们底层是同一套引擎,WAAPI 只是多开了一扇门让你从 JS 那头也进来操作。说到让组件自己响应变化、而不是被全局状态牵着走,这种"各管各的、互不耦合"的思路,其实和 CSS 容器查询实战:让组件响应自己的容器而非视口(@container 与 cqw)里讲的组件级响应式是一脉相承的——都是"把控制权下放到更合适的地方"。

九、WAAPI 和 CSS @keyframes 到底啥关系

很多人学 WAAPI 会陷入一个误区:是不是以后全用 JS 写动画,把 CSS 那套扔了?我的结论是互补不替代,分场景:

  • 纯装饰、不依赖状态的循环动画:比如 loading 转圈、背景飘带,老老实实用 CSS @keyframes。它写在样式表里,和 JS 逻辑解耦,首屏就能跑,浏览器优化也成熟,没必要非用 JS 去 animate()。
  • 交互型、需要被程序精确控制的动画:比如可拖拽进度的入场、可暂停的教学演示、配合业务状态变化的反馈动效,上 WAAPI。你能拿到 Animation对象,能currentTime定位、playbackRate调速、finished 编排,这是 CSS 给不了的。
  • 已经用 CSS 写好的动画,想临时接管:用getAnimations()抓出来再控制,不用重写。

还有个常被忽略的点:CSS 动画其实也有事件——animationend、animationiteration、animationstart,挂在元素上用addEventListener 能监听。但和 WAAPI 比,CSS 的"控制粒度"是粗的:你能知道它"播完了",但播到哪一帧、能不能指定位置停,CSS 无能为力。WAAPI 把"查询"和"指挥"两个能力都补齐了。

再补一刀:CSS 动画也能通过 transition产生过渡动画,而getAnimations() 抓回来的数组里同样包含这些过渡——也就是说你连 hover 触发的 transition 都能用 JS 接管、暂停。另外经常有人纠结"WAAPI 动画有没有硬件加速",答案是:和 CSS 动画一模一样,凡是涉及 transform、opacity 这类合成层属性的动画,浏览器照样走 GPU 合成,性能并不会比 CSS 差。所以性能焦虑也可以放一放了。

顺带说下兼容性,免得你心里打鼓

不少人一听"浏览器原生 API"就担心兼容性。实话实说,WAAPI 的核心能力(前面讲的那一整套)早已是跨主流浏览器的标配:Chrome、Edge 从很早就完整支持,Firefox 也覆盖到位,Safari 从 13.1 开始基本齐活,移动端 iOS 与 Android 的现代版本同样没问题。写之前你可以用一句 if (typeof element.animate === 'function') 做个特性探测,不支持就优雅降级回 CSS 动画,支持的就敞开用。真要抠细节,个别边缘属性(比如某些 composite 合成模式)老版本可能不全,但本文聊的播放控制、定位、调速、Promise 编排,全是稳定可用的老接口,放心。

所以我的实践心法是:静态的交给 CSS,动态的交给 WAAPI,已经存在的 CSS 动画用 getAnimations 接管。别一上来就觉得"动画不够底层"非要去折腾WebGPU 计算着色器实战:用 WGSL 在浏览器里跑并行计算(矩阵乘法与工作组) 那种 GPU 并行方案——对 99% 的 UI 动效来说,WAAPI 这点控制力早就绰绰有余了,杀鸡别用牛刀。

十、综合实战:一个可暂停、可拖拽、可调速的播放器

光讲 API 不过瘾,我把前面这些串起来,做一个"动画播放器":能播放/暂停、能拖进度、能切倍速。这基本就是把当年那个让我破防的需求,用 WAAPI 干净利落地解决掉。

javascript
复制
// ---- 综合实战:可暂停 / 可拖拽 / 可调速的动画播放器 ----// 1) 创建动画(这里用 WAAPI 而非 CSS,方便全程接管)const player = document.querySelector('#player');const anim = player.animate( [ { transform: 'translateX(0)', opacity: 0 }, { transform: 'translateX(200px)', opacity: 1 }, { transform: 'translateX(400px)', opacity: 0 } ], { duration: 4000, easing: 'ease-in-out', fill: 'forwards' });// 2) 播放 / 暂停按钮const toggleBtn = document.querySelector('#toggle');toggleBtn.addEventListener('click', () => { if (anim.playState === 'running') { anim.pause(); toggleBtn.textContent = '播放'; } else { anim.play(); toggleBtn.textContent = '暂停'; }});// 3) 进度滑块:拖动即时定位const slider = document.querySelector('#slider');slider.addEventListener('input', (e) => { const total = anim.effect.getComputedTiming().duration; anim.currentTime = (e.target.value / 100) * total; // 0~100 映射到 0~duration});// 4) 倍速按钮组document.querySelectorAll('[data-rate]').forEach(btn => { btn.addEventListener('click', () => { anim.playbackRate = Number(btn.dataset.rate); // 0.5 / 1 / 2 自由切 });});// 5) 播完收尾:点亮"完成"anim.onfinish = () => { document.querySelector('#done').classList.add('active');};// 6) 同步滑块:动画播放时让滑块跟着走(用 requestAnimationFrame 轮询 currentTime)function syncSlider() { if (anim.playState === 'running') { const total = anim.effect.getComputedTiming().duration; slider.value = (anim.currentTime / total) * 100; } requestAnimationFrame(syncSlider);}syncSlider();

就这么百来行,当年我用 CSS + setTimeout写了三百多行还没搞定的东西,全解决了。而且逻辑清清楚楚:一个anim 对象贯穿始终,没有散落各处的 class 切换,没有竞态。

再延伸两个我在真实项目里用到的组合:一是多标签页同步——如果你希望用户在 A 标签页暂停了动画,B 标签页也跟着停(比如一个长教程演示),可以用 BroadcastChannel 实战:同源多标签页实时通信(登录态同步与状态广播)把pause/play指令广播出去,各标签页监听到就调用自己的anim.pause();二是进度持久化——用户拖到一半关了页面,下次进来想接着看?把currentTime和playbackRate存起来,刷新不丢,这部分直接交给IndexedDB 前端数据库实战:离线应用与索引查询(增删改查与事务)做本地持久化最稳。

哦对了,播放器里那些进度数字、倍速文字如果想按用户所在地区格式化显示(比如德语里小数点用逗号、某些地区用逗号做千分位),别自己手搓格式化函数,前端国际化实战:Intl API 与 i18n 架构(数字/日期/货币与动态切换)里讲的Intl.NumberFormat一行就能搞定,和动画控制是正交的两块能力,配合起来很舒服。

十一、踩坑小结 / 最佳实践

写到这,WAAPI 的核心我基本讲完了。最后把我自己踩过的坑和沉淀下来的经验,压缩成几条,方便你上手就避雷:

  1. 别用不存在的 API。网上有些教程写的AnimationGroup、createSequence不是标准,跑不起来。串动画就老老实实await anim.finished,标准且可靠。
  2. pause和cancel想清楚再选。能恢复的用pause,永久结束的才用cancel;cancel会让finished Promise 走 reject,捕获时别当成异常 swallowed。
  3. currentTime单位是毫秒,是浮点数。读出来做运算注意精度,定位前先确认playState,定位后想继续播记得play()。
  4. playbackRate不改duration。调速靠它,别去改时间轴长度,否则正在播的动画会跳。
  5. updateTiming是改时间轴,平滑不断播。想运行时改时长/缓动用它,比重新animate()干净。
  6. CSS 动画也能管。element.getAnimations()/document.getAnimations() 把 CSS 驱动的动画也抓出来,无障碍"降低动效"开关就靠它。
  7. 静态归 CSS,动态归 WAAPI。 别走极端全用 JS,也别死守 CSS 不求变,互补才是正解。

还有两个工程层面的小提醒,都是我交过"学费"才记住的。一是动画对象记得及时清理:长列表里频繁animate()却不cancel释放,累积多了会有内存压力,尤其是iterations: Infinity的循环动画,离开视口就该cancel()掉。二是别在每一帧用currentTime去做重型计算,syncSlider那种requestAnimationFrame轻量轮询已经够用,真要高频同步,优先直接读currentTime 而非额外维护一堆状态变量,既省心又不易出 bug。

WAAPI 上手成本极低——你只要记住 element.animate()拿到Animation对象,然后play / pause / currentTime / playbackRate / updateTiming / finished 这六个抓手,基本上 UI 动效里 90% 的"精确控制"需求都能拿下。当年那个让我破防的需求,现在回头看,其实就是没早一点认识这把"手术刀"。希望这篇能帮你少走我那段弯路。

标签:Web Animations API, WAAPI, animate, 动画控制, getAnimations描述:用第一人称踩坑叙事讲透 Web Animations API:从 element.animate() 创建动画,到 play/pause/reverse/cancel/finish 播放控制、currentTime 精准定位、playbackRate 调速、updateTiming 运行时改时长、finished Promise 编排,再到 getAnimations() 接管 CSS 动画,并厘清 WAAPI 与 CSS @keyframes 的互补关系,附一个可暂停/拖拽/调速的实战播放器。

n9ihqx6madn9ihqx6mad
评论

评论列表共有0条评论

发表评论发布评论后审核成功,即可显示在列表中...

文章1
粉丝0
获赞0
私信