页面流畅和FPS
-
页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿。
-
1s 60帧,所以每一帧分到的时间是 1000/60 ≈ 16 ms。所以我们书写代码时力求不让一帧的工作量超过
16.7ms。
FPS
我们看下浏览器每一帧都处理什么
- 1.响应交互
- 2.JS解析执行
- 3.帧处理开始,窗口尺寸变化、窗口滚动等
- 4.执行requestAnimationFrame(rAF)
- 5.布局(重构/回流)
- 6.绘制(重绘)
如果上面的6步完成后,还有剩余时间,就会执行requestIdleCallback的回调函数
requestAnimationFrame
先看看使用方法
var ele = document.getElementById("test")
var progress = 0;
function step(timestamp) {
progress++;
ele.style.width = progress + "%";
if (progress < 100) {
requestAnimationFrames(step)
}
}
requestAnimationFrames(step)
上面这个例子,id为test的元素,width从0%增长到100%
API
window.requestAnimationFrame(callback)
-
callback:回调函数,接收一个参数(DOMHighResTimeStamp)
DOMHighResTimeStamp: 表示开始执行这个回调函数的时刻
和setTime、setInterval比较
在没有requestAnimation方法之前,可以使用setTime或setInterval来触发视觉变化,但是这种方法的问题:回调函数执行的时间是不固定的,可能刚好就在末尾,或者直接就不执行了,经常会引起丢帧而导致页面卡顿
-
时机不同
setTimeout/setInterval:使用定时器来触发回调函数,而定时器并无法保证能够准确无误的执行。比如同步代码执行太久。如果定时器时间间隔过短,会导致过度渲染。过长又会延迟渲染,导致不流畅requestAnimationFrame:是由系统来决定回调函数的执行时机,会请求浏览器在下一次重新渲染之前执行回调函数。时间间隔会紧跟屏幕刷新一次需要的时间。但是要注意:如果回调(也就是这一帧)中执行太多任务,还是会造成卡顿
总结
- 通常用来
执行一个动画,浏览器下次重绘之前调用回调函数更新动画 - 如果浏览器不支持这个方法,可以用setTimeout模拟
- 每一帧渲染都
执行一次 - 系统决定调用回调函数,
时间间隔固定
兼容
if (!window.requestAnimationFrame) {
var lastTime = 0;
window.requestAnimationFrame = function(callback) {
// 当前时间
var currTime = new Date().valueOf();
// 时间间隔
var interval = Math.max(0, 16.7 - (currentTime - lastTime))
var id = window.setTimeout(function() {
// 执行回调函数,并把执行的时间作为参数传回去
callback(currTime + interval)
}, interval)
lastTime = currTime + interval;
return id;
}
}
requestIdleCallback
先来个例子,看下使用方法
var tasksNum = 10000
function work(deadline) {
while (deadline.timeRemaining() && tasksNum > 0) {
console.log(`执行了${10000 - tasksNum + 1}个任务`)
tasksNum--
}
if (tasksNum > 0) { // 在未来的帧中继续执行
requestIdleCallback(unImportWork)
}
}
requestIdleCallback(work)
API
var handle = window.requestIdleCallback(callback[, options])
-
callback: 接受的回调函数,回调函数里面有一个参数(IdleDeadline)包含:
timeRemaining():当前帧剩余的空闲时间didTimeout: boolean值,表示是否已经超时了,这个会根据传入的第二个参数决定
-
options: 对象只有一个参数
timeout: 毫秒,超时时间,如果超过这个时间,就会强制执行,不会等待空闲
总结
- 一些低优先级的任务可以使用requestIdleCallback,但是同时因为时间有限,接受的任务尽量是可以很快执行的
同步或者微任务 - 因为是每一帧结束后执行,已经渲染完成,所以这个时候尽量
不要操作DOM,减少不必要的重复渲染 - Promise也不建议在这里执行,因为回调是微任务,会很快执行,时间不可控,可能会超过一帧时间,造成卡顿
区别
-
是否执行
requestAnimationFrame:每一帧都会执行requestIdleCallback:只有在每一帧执行完后,有剩余时间,才会执行,是捡浏览器空闲时间执行,所以不一定会执行。也就是如果浏览器一直非常的忙碌,回调方法可能永远不会执行