requestAnimationFrame
浏览器每次刷新页面之前执行其中的回调函数,宏任务。返回一个整数编号,可用于传递给cancelAnimationFrame取消动画帧。
- requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
保证其回调函数在屏幕每一次的绘制间隔中执行一次,不会出现丢帧和动画卡顿的情况。
- 隐藏的元素不会触发回调。
保证更少的 CPU、GPU 消耗。
- 浏览器专门为动画提供的 API,浏览器在运行时会针对该 API 进行优化,如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销。
setTimeout 和 setInterval
- setTimeout 和 setInterval 定时器是不精准的,设置的间隔时间其实是在线程队列中等待的时间。由于 js 的单线程机制,在插入队列前有大量任务或者其中某个任务的执行时间过长,都会影响最终的执行时间。
- 并且浏览器为了防止过多任务的堆积,对其最小间隔时间做了限制(4ms/10ms),即使你设置 setTimeout(fn, 0),回调函数也不会立即执行,而是至少在 4 毫秒后执行。
- setInterval 因为其重复执行,其误差值可能会越来越大。
小结
js 事件循环前序队列事件的阻塞、延迟,浏览器的资源调度策略(最小间隔)等问题都会影响 setTimeout 和 setInterval 的精度。
requestAnimationFrame
执行回调与系统的绘制帧率同步(保持最佳绘制效率),不会因为间隔时间过短导致过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅。让网页动画有一个统一的刷新机制,节约系统资源,提高系统性能,改善视觉体验。
应用
页面滚动事件(*)
平滑回滚到页面顶部(在 requestAnimationFrame 事件中每次回滚一小段,直到回到顶部):
const scrollToTop = () => {
const c = document.documentElement.scrollTop || document.body.scrollTop;
console.log(c);
if (c > 0) {
window.requestAnimationFrame(scrollToTop); // 平滑回滚
window.scrollTo(0, c - c / 8);
// window.scrollTo(0, c - c);
}
};
document.addEventListener("scroll", () => {
window.requestAnimationFrame(() => {
console.log("scroll");
});
});
大量数据渲染
一般做法:使用定时器(setTimeout/setInterval)控制循环 loop 函数,在 loop 函数中遍历添加限制长度的数据并添加到页面中。
或者使用分页请求,前端设置 pageSize,每次点击不同的 pageCount 就重新请求一次。
优化:使用 requestAnimationFrame 代替 setTimeout。
//需要插入的容器
const ul = document.getElementById("container");
// 插入十万条数据
const total = 100000;
// 一次插入 20 条
const once = 20;
//总页数
const page = total / once;
//每条记录的索引
const index = 0;
//循环加载数据
function loop(curTotal, curIndex) {
if (curTotal <= 0) {
return false;
}
//每页多少条
const pageCount = Math.min(curTotal, once);
function addData() {
for (let i = 0; i < pageCount; i++) {
let li = document.createElement("li");
li.innerText = curIndex + i + " : " + ~~(Math.random() * total);
ul.appendChild(li);
}
loop(curTotal - pageCount, curIndex + pageCount);
}
window.requestAnimationFrame(addData);
// setTimeout(addData, 0);
}
loop(total, index);
监控页面卡顿(监测性能)
定义一些边界值来检测网页的帧率(fps),通过在 requestAnimationFrame 中执行一些代码,并计算执行时间来判断是否页面卡顿。(requestAnimationFrame 的绘制率是 60HZ,大多数人的视觉系统能够识别的最大帧率为 60 帧每秒)
1s 中 60 帧,1 帧 16.7ms,如果超过 16.7ms,则表示页面卡顿。
水印(*)
使用 requestAnimationFrame 添加页面水印(背景图片)。