前言
之前写过一篇《讲解 CSS 过渡和动画 transition/animation》 文章,其中提到,在现如今用户越来越注重 Web应用 使用体验的常态下,Web 动画
的发展不再是 Flash Player
为主,早已是 HTML5
的标准大放异彩的世界。 而在浏览器中主要由这两种 CSS 动画
和 JS 动画
进行实现:
-
CSS 动画
-
CSS Transition:
CSS 过渡,属于 补间动画,即设置关键帧的初始状态,然后在另一个关键帧改变这个状态,比如大小、颜色、透明度等,浏览器将自动根据二者之间帧的值创建的动画。 -
CSS Animation
CSS 动画,可以理解是CSS Transition
的加强版,它既可以实现 补间动画 的动画效果,也可以使其以 逐帧动画 的方式进行绘制。
-
-
JS 动画
(本篇主题)-
setTimeout / setInterval / requestAnimationFrame
我们最常用的是setTimeout
和setInterval
这两个API。但是这两个 API 设定的时间会因为浏览器当前工作负载而有所偏差,而且无法与浏览器的绘制帧保持同步。所以才有了 与浏览器的绘制帧同步 的原生 APIrequestAnimationFrame
,以取代setTimeout
和setInterval
实现动画。 -
Animations API
浏览器动画引擎 API,通过 JavaScript 操作。这些 API 被设计成CSS Transition
和CSS Animation
的接口,很容易通过 JS 的方式实现 CSS 动画,可以理解成 CSS 动画的 JS版本。它是目前对动画化的支持最有效的方式之一。
-
其中 CSS 动画
更多细节内容在之前的文章中已进行了分析和讲解。而本篇将主要以 JS 动画
的视角对如下问题进行分析和探讨:
- JS 动画 相关的 API 有哪些?
- JS 动画 和 CSS 动画 相比,性能真的比不上吗?
- JS 动画 和 CSS 动画 相比,都有哪些优势和劣势?
- JS 动画 有了 requestAnimationFrame API, 为什么还需要 Animations API?
- JS 动画 的 Animations API 的定义和使用
JS 动画 API 有哪些
setTimeout / setInterval API
设定定时器,通过周期性的触发重复执行绘制动画的函数,来实现 “逐帧动画” 的效果。
// 定时 id
let id = null
// 动画函数
const draw = () => {
/* 动画绘制... */
}
// setTimeout
const start = () => {
draw()
clearTimeout(id)
id = setTimeout(start, 16.7)
}
const stop = () => { clearTimeout(id) }
// setInterval
const start = () => {
clearInterval(id)
id = setInterval(draw, 16.7)
}
const stop = () => { clearInterval(id) }
-
优势
- 具有很好的浏览器兼容性,所有浏览器都支持这两个 API
- 这两个API 通过回调函数的定义,可以很方便的实现动画效果
-
劣势
- 只能接近设备屏幕刷新率,无法做到和浏览器同步,所以可能会存在卡顿、丢帧、抖动的现象
- 由于浏览器单线程机制,存在队列中回调函数被阻塞的可能,所以无法保证每一次调用的时间间隔都相同,某次回调可能会被跳过,导致跳帧。
- 即使将浏览器最小化,或者切换到另一个 Tab 页面,定时器还依旧会继续在后台执行,浪费了 CPU 的资源。
requestAnimationFrame API
为了弥补 setTimeout / setInterval
在动画方面的不足,所以浏览器提供了为动画而生的 API,它可以让 DOM 动画、Canvas 动画、 SVG 动画、WebGL 动画等有一个统一的刷新机制,随着浏览器的屏幕刷新,统一绘制动画帧。
// 定时 id
let id = null
// 动画函数
const draw = () => {
/* 动画绘制... */
}
// requestAnimationFrame
const start = () => {
draw()
cancelAnimationFrame(id)
id = requestAnimationFrame(start)
}
const stop = () => { cancelAnimationFrame(id) }
-
优势
- 由系统来决定回调函数的执行时机, 它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次, 这样就不会引起丢帧现象, 也不会导致动画出现卡顿的问题。
- 在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU的开销。
-
不足
- 同 setTimeout/setInterval 一样,它是以逐帧动画的方式进行绘制,无法做到像 CSS 动画,让游览器自动根据两帧之间的差异创建插值,以实现补间动画的过渡效果。
Animations API
既然已经有了为动画而生的 requestAnimationFrame
,为什么还需要 Animation API 呢?我想原因大致如下:
-
requestAnimationFrame
、setTimeout/setInterval
都是以逐帧绘制的方式实现动画, 而 Animations API 不仅可以 “逐帧动画”,还可以实现 “补间动画” 的效果。 -
CSS 动画有一定的局限性,需要事先预设动画样式,而且无法与 JS 进行交互。相比之下,Animations API 可以随时定义并使用动画,自然是更加灵活方便。
有关 Animation API 的定义和使用,后面会在 JS Animations API 章节中讲解。
JS 动画 相关几个问题
JS 动画 性能如何?
其实比较 JS 动画 和 CSS 动画的性能,两者并没有绝对的高低。就应用场景而言,简单的动画通过 CSS 语法去实现可能更好,复杂的动画通过 CSS 去实现,则代码冗余不说,性能可能远不如 JS 语法实现。
那为什么会有 CSS 动画性能比 JS 动画性能更好的说法呢?我列了下如下的几种可能供大家参考:
-
由于
setTimeout/setInterval
无法与浏览器每帧绘制保持同步,所以可能会存在卡顿、丢帧、抖动的现象,导致动画体验不如 CSS3 动画。 -
由于
setTimeout/setInterval/requestAnimationFrame
的动画实现属于逐帧动画
,需要计算出每一帧的动画位置或状态值。相比 CSS3 的补间动画
,则是由浏览器根据两帧之间的差异自动计算并绘制动画,性能更胜一筹。 -
CSS 部分属性不触发 pain/layout(即重绘和重排), 例如 opacity、transform 只涉及到浏览器的 compositor thread 的合成工作,性能大幅度提升。
-
CSS 部分属性能够启动3D加速和GPU硬件加速,例如使用 transform 的 translateZ 进行3D变换时。
如上所述,这也许就是有了 requestAnimationFrame 之后,还出现了 Animations API 的原因之一吧。
JS/CSS 动画 优缺点?
-
JS 动画
优点
- JS 动画控制能力很强, 可以在动画播放过程中对动画进行控制:开始、暂停、回放、终止、取消都是可以做到的。
- JS 动画效果比 CSS 动画丰富,有些动画效果(比如复杂曲线运动, 视差滚动效果),由 JS 实现更加容易和定制。
缺点
- JavaScript 在浏览器的主线程中运行,而由于主线程中还有其它工作(样式计算、布局、绘制等), 对其干扰导致线程可能出现阻塞,从而造成丢帧的情况。
- 动画的实现复杂度高于 CSS 动画
-
CSS 动画
优点
- 浏览器可以对 CSS 动画进行优化。
- 通过
transform
、translateZ
等属性开启硬件加速 (使用 GPU 提高性能)。
缺点
- CSS 动画的控制力较弱,只能对其进行暂停或恢复,不能在半路反转动画,也无法对其动画中的关键帧添加事件加以监听。
- 用 CSS 实现稍微复杂一点的动画,会相当困难,且代码会变得异常笨重。
JS Animations API
概述/简介
浏览器动画引擎 API,通过 JavaScript 操作。这些 API 被设计成 CSS Transition
以及 CSS Animation
的接口,很容易通过 JS 的方式实现 CSS 动画,可以理解成 CSS 动画的 JS版本。它是目前对动画化的支持最有效的方式之一。
语法定义
-
Animation
/** * effect: KeyframeEffect 对象 * timeline: 指定与动画关联的时间轴,默认 document.timeline */ var animation = new Animation(effect, timeline);
-
animation 属性
currentTime
:动画的当前时间值(以毫秒为单位)。effect
:获取或设置与此动画相关联的 KeyframeEffect。finished
:返回此动画的当前完成的状态(只读)。id
:获取或设置用于标识动画的字符串。playState
:返回描述动画播放状态的枚举值。playbackRate
:返回或设置动画的播放速率。ready
:返回此动画的当前就绪状态 (只读)。startTime
:获取或设置动画播放应开始的预定时间。timeline
:获取或设置与此动画相关联的 timeline。
-
animation 方法
cancel()
:清除此动画的所有 keyframeEffects 并中止播放。finish()
:结束动画播放。pause()
:暂停播放动画。play()
:开始或恢复播放动画,或者如果之前完成,则重新开始动画。persist()
:显式保留动画,防止在另一个动画替换它时自动删除。reverse()
:反转播放动画,直到播放到动画开始时停止。
-
animation 事件
cancel
:获取并设置取消事件的事件处理程序。finish
:获取并设置完成事件的事件处理程序。remove
: 当浏览器自动删除动画时触发。
-
-
KeyframeEffect
/** * target: 要进行动画处理的 DOM 元素 * * keyframes: 关键帧对象 * * options: 表示动画持续时间整数(ms),或包含以下一项或多项的对象 * * delay: 延迟动画开始的毫秒数 * direction: 动画运行播放方向 * easing: 动画随时间变化的速率 * endDelay: 动画结束后延迟的毫秒数 * fill: 设置 CSS 动画在执行之前和之后如何将样式应用于其目标 * iterationStart: 描述动画应在迭代中的哪个点开始 * iterations: 动画应重复的次数 * pseudoElement: 伪元素选择器 如 ::before * iterationComposite: 确定在此动画中如何从迭代到迭代生成值 * composite:确定此动画与其他单独的值之间的组合方式 * * sourceKeyFrames: 要克隆的关键帧效果对象。 * */ const keyframeEffect = new KeyframeEffect(target, keyframes) const keyframeEffect = new KeyframeEffect(target, keyframes, options) const keyframeEffect = new KeyframeEffect(sourceKeyFrames)
-
keyframeEffect 属性
target
:获取和设置由此对象进行动画处理的伪元素的原始元素。pseudoElement
:获取和设置此对象要进行动画处理的伪元素的选择器。iterationComposite
:获取和设置迭代复合操作,解析关键帧效果属性值。composite
:获取和设置迭代复合操作,解析关键帧效果属性值。
-
keyframeEffect 方法
getComputedTiming
:返回此关键帧效果的计算的当前计时值。getKeyframes
:返回构成此效果的计算关键帧及其计算的关键帧偏移量。getTiming
:返回与包含所有动画计时值的动画关联的对象。updateTiming
:更新指定的计时属性。setKeyframes
:替换构成此效果的关键帧集。
其中 options 参数选项,请参考 CSS animation-* 属性
-
语法范例
-
new Animation
const element = document.getElementById("container"); const keyframes = new KeyframeEffect( element, [ { transform: "translateY(0%)" }, { transform: "translateY(100%)" }, ], { duration: 3000, fill: "forwards" } ); const animation = new Animation( keyframes, document.timeline ); // Auto Play
-
Element.animate
(简写方式,推荐)const element = document.getElementById("container"); const animation = element.animate( [ { transform: "translateY(0%)" }, { transform: "translateY(100%)" }, ], { duration: 3000, fill: "forwards" } ); // Auto Play
代码范例演示