动画
web开发中实现动画的方式有多种,CSS3中的transition和animation,js中的setInterval、setTimeout、canvas。H5新API requestAnimationFrame。
屏幕的刷新频率
屏幕的刷新频率即图像在屏幕上每秒更新的次数,单位为HZ。该值受屏幕分辨率、屏幕尺寸和显卡的影响。每一次称之为帧,一般的屏幕为60HZ,则一帧的时间为16.7ms左右。
setInterval动画存在的问题
setInterval 其实就是通过设置一个间隔时间来不断的改变图像的位置,从而达到动画效果的。但有时候会发现,利用setInterval实现的动画在某些低端机上会出现卡顿、抖动的现象。 这种现象的产生有两个原因:
setInterval 的执行时间并不是确定的。js执行时,setInterval 会被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setInterval 的实际执行时间一般要比其设定的时间晚一些。
屏幕的刷新频率受分辨率和尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setInterval 只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同,所以会引起丢帧从而出现卡顿现象。
requestAnimationFrame
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
优势:
requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点:
- requestAnimationFrame 是由系统来决定回调函数的执行时机。它会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随屏幕的刷新频率,不会引起丢帧和卡顿。
- 使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。
用法:
window.requestAnimationFrame(callback)
回调参数:
回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。
返回值:
返回整数,即请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。
案例:
- 普通用法:
<div class="box"></div>
<button class="btn">click</button>
const box = document.querySelector('.box')
const btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
let start
function handler(timestamp) {
if (!start) start = timestamp
const progress = timestamp - start
start = timestamp
console.log(progress)
if (box.offsetWidth < 100) {
box.style.width = `${box.offsetWidth + 1}px`
box.innerHTML = box.offsetWidth + '%'
requestAnimationFrame(handler)
}
}
requestAnimationFrame(handler)
})
- 取消回调
<div class="main">
<div class="content"></div>
<button class="button">click</button>
</div>
const content = document.querySelector('.content')
const button = document.querySelector('.button')
button.addEventListener('click', () => {
let rfaId
let left = 0
function handler() {
if (content.offsetWidth < 100) {
content.style.left = `${left++}px`
if (left >= 100) {
return cancelAnimationFrame(rfaId)
}
rfaId = requestAnimationFrame(handler)
}
}
rfaId = requestAnimationFrame(handler)
})
- 自定义raf时间
自定义raf时间通常是放慢刷新频率,毕竟系统的16.7ms已经是正常的,再快也没什么意义。
<div class="main"></div>
<button class="btn">click</button>
const element = document.querySelector('.main')
const btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
// 自定义的动画时间差
const time = 50
let left = 0
let flag = true
// 当前执行时间
let nowTime = Date.now()
// 每次动画执行结束的时间
let lastTime = Date.now()
// 执行动画
function handler() {
nowTime = Date.now()
if (nowTime - lastTime >= time) {
lastTime = nowTime
if (flag) {
if (left <= 100) {
element.style.left = `${left++}px`
} else {
flag = false
}
} else {
if (left >= 0) {
element.style.left = `${left--}px`
} else {
flag = true
}
}
}
requestAnimationFrame(handler)
}
requestAnimationFrame(handler)
})
当然,该API的用途不止设置动画,还可以用于节流、防抖等。比如lodash关于节流、防抖的就是基于此API实现的。