最近工作中经常遇到动画的情况,之前常用的方法使用setTimeout或setInterval实现,但随着应用的越来越复杂,性能方面就会降低。所以选择使用requestAnimationFrame来实现相同效果。本文简单记录使用rAF的方法。
requestAnimationFrame是HTML5中提供的动画API,简称rAF,即请求动画帧。可以让浏览器优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果。说rAF之前先来简单了解与之相关的几个概念。
屏幕刷新频率
屏幕的刷新频率可在电脑中“高级显示设置”中查看,一般为60Hz,就是说,屏幕静置情况下,显示器会以每秒60次的频率不断更新屏幕上的图像,因为人的“视觉停留效应”,并感觉不到变化或者抖动,看到的仍是一幅幅连续的画面,其实这中间间隔时间是16.7ms(即1000/60)。
我们知道动画的本质就是让人看到图像在连贯、平滑地变动,那根据上面我们知道的,16.7ms屏幕刷新一次,在刷新时让图像移动1px,这样在视觉效果上便形成了动画。
setTimeout
从上面的介绍,我们可以了解,setTimeout其实就是通过一个时间间隔来不断更新图像形成的动画效果。但setTimeout在某些机型或复杂应用中会出现卡顿现象,也就是常说的“丢帧”。这事因为setTimeout只能设置固定的时间间隔,而不同的屏幕、机型会有不同的分辨率,而且setTimeout任务是被放进异步队列中的,所以实际执行时间会比设定时间晚一点。这些原因就导致了setTimeout动画的卡顿现象。
requestAnimationFrame
知道了setTimeout的缺点,rAF的出现就顺理成章了。rAF的回调函数执行时机由系统决定,也就是说,系统每次绘制前都会主动调用rAF中的回调函数,如果系统绘制频率是60Hz,那回调函数就是16.7ms被执行一次,如果系统绘制频率是75Hz,那么这个时间间隔就是1000/75=13.3ms,这样就保证回调函数在每次绘制中间都能执行一次,就不会出现丢帧的现象,也不会有卡顿的问题。
这个API的使用方法也很简单,如下:
var progress = 0;
//回调函数
function render() {
progress += 1; //修改图像的位置
if (progress < 100) {
//在动画没有结束前,递归渲染
window.requestAnimationFrame(render);
}
}
//第一帧渲染
window.requestAnimationFrame(render);
优雅降级
因为rAF是HTML5提供的新API,目前还存在兼容性问题,所以需要对其进行优雅降级处理,这里提供网上的一些比较成熟的解决方案:
;(function () {
var lastTime = 0
var vendors = ['ms', 'moz', 'webkit', 'o']
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']
window.cancelAnimationFrame =
window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime()
var timeToCall = Math.max(0, 16 - (currTime - lastTime))
var id = window.setTimeout(function () {
callback(currTime + timeToCall)
}, timeToCall)
lastTime = currTime + timeToCall
return id
}
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function (id) {
clearTimeout(id)
}
})()
以上来自tweenjs提供的RequestAnimationFrame.js https://github.com/tweenjs/tween.js/blob/master/examples/js/RequestAnimationFrame.js
实例
下面通过一个简单的例子来演示rAF的用法。
在html内设置一个圆,然后让其动起来。
<!DOCTYPE html>
<html>
<head>
<title>rAF</title>
<style>
* {margin: 0;padding: 0}
.box {width: 100px;height: 100px;border-radius: 100%;background: #f00;position: absolute;left: 0;top: 0}
</style>
</head>
<body>
<div class="box" id="box"></div>
</body>
</html>
简单的方式如下:
var box = document.getElementById('box')
var flag = false
var left = 0
function render() {
if (flag) {
if (left >= 100) {
flag = false
}
box.style.left = `${left++}px`
} else {
if (left <= 0) {
flag = true
}
box.style.left = `${left--}px`
}
}
(function animloop() {
render()
window.requestAnimationFrame(animloop)
})()
以上便可让html中的圆形左右动起来。
如果想让动画停下来需要怎么处理呢,是否可以像clearInterval这样的方式呢?其实rAF提供了cancelAnimationFrame方法来停止动画处理,requestAnimationFrame默认会返回一个id,将id传入cancelAnimationFrame中便可达到效果。
修改以上代码:
var box = document.getElementById('box')
var flag = false
var left = 0
var rAFId = ''
function render() {
if (flag) {
if (left >= 100) {
flag = false
}
box.style.left = `${left++}px`
} else {
if (left <= 0) {
flag = true
}
box.style.left = `${left--}px`
}
}
(function animloop() {
render()
rAFId = window.requestAnimationFrame(animloop)
if (left == 50) {
cancelAnimationFrame(rAFId)
}
})()
这样在圆运动到距左侧50px的时候就停下来了。
自己的学习笔记,如果你刚好看到,希望也能帮着你,如有不正确的地方欢迎多多指出。