什么是请求动画帧
requestAnimationFrame 它是一个浏览器的宏任务;
requestAnimationFrame 的用法与 setTimeout 很相似,只是不需要设置时间间隔而已;
requestAnimationFrame 使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用;
requestAnimationFrame 返回值是一个整数,表示定时器的编号,这个值可以传递给 cancelAnimationFrame 用于取消这个函数的执行;
请求动画帧的特点
requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率;
- 如果系统绘制率是 60Hz,那么回调函数就会间隔 16.7ms 再被执行一次,如果绘制频率是 75Hz,那么这个间隔时间就变成了 1000/75=13.3ms ;
- 换句话说就是 requestAnimationFrame 的执行步伐跟着系统的绘制频率走,它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题;
requestAnimationFrame 会将回调函数加入到浏览器下一次重绘之前要执行的队列中,这样做的目的是为了确保动画的流畅度,因为浏览器会自动优化这个 API,只在浏览器处于激活状态并且页面处于可见状态时才会执行回调函数;
请求动画帧的优缺点
优点
:
- 让各种网页动画效果(DOM 动画、Canvas 动画、SVG 动画、WebGL 动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果;
- 一旦页面不处于浏览器的当前标签,就会自动停止刷新,这就节省了 CPU、GPU;
- 在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流;
缺点
:
- requestAnimationFrame 是在主线程上完成,这意味着,如果主线程非常繁忙,requestAnimationFrame 的动画效果会大打折扣;
对比 setTimeout 和 setInterva
执行时机
:
- requestAnimationFrame 是由浏览器提供的 API,它会在浏览器下一次重绘之前执行回调函数,这意味着它能够确保动画的流畅度,并且能够自动匹配系统的刷新率;
- 相比之下,setTimeout 和 setInterval 会在指定的时间间隔后执行回调函数,无论浏览器是否处于激活状态或正在进行其他操作;
性能优化
:
- requestAnimationFrame 由浏览器自动优化,只在浏览器处于激活状态并且页面处于可见状态时才会执行回调函数,这可以节省 CPU、GPU 和 内存 的使用,特别是在移动设备上;
- 相比之下,setTimeout 和 setInterval 不会自动优化,如果页面处于隐藏或不可见状态,它们会继续执行回调函数,这可能会导致资源的浪费;
回调函数执行时间
:
- requestAnimationFrame 的回调函数会在浏览器下一次重绘之前执行,因此它能够确保回调函数的执行时间相对准确;
- 相比之下,setTimeout 和 setInterval 的回调函数执行时间取决于浏览器事件循环中的队列和执行时间,因此可能会有一定的延迟;
停止操作
:
- requestAnimationFrame 的回调函数只会在浏览器下一次重绘之前执行一次,因此可以通过清除队列中的回调函数来停止操作;
- 相比之下,setTimeout 和 setInterval 会不断地执行回调函数,直到 clearInterval 或 clearTimeout 被调用或关闭页面为止;
函数节流:
- requestAnimationFrame 在高频率事件中(resize, scroll等),可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销;
兼容性处理
IE9 浏览器不支持该方法,可以使用 setTimeout 来兼容;
window.requestAnimationFrame = (function () {
return window.requestAnimationFrame || funcion(callBack) {
window.setTimeout(callBack, 1000 / 60);
}
})();
window.cancelAnimationFrame = (function () {
return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || funcion(id) {
window.clearTimeout(id);
}
})();
应用场景
使用场景 | |
---|---|
setTimeout | · 可用于在网页加载后延迟执行某些操作,例如加载页面内容、初始化组件等 · 也可用于定时触发某些操作,例如定时发送数据、定时检查任务等 |
setInterval | · 常用于需要周期性执行的操作,例如定时更新数据、定时触发事件等 · 在 web 端,如果列表需要定时更新,可以使用 setInterval 来定时获取列表的请求;另外,如果需要在某一特定情况下清除定时任务,可以使用 clearInterval 来停止定时器 |
requestAnimationFrame | · 主要用于实现流畅的动画效果,它会在浏览器下一次重绘之前执行指定的函数,避免了频繁的重绘导致的性能问题 · requestAnimationFrame 会自动匹配系统的刷新率,从而确保每帧动画的间隔时间尽可能地准确 · 在需要反复触发的情况下,使用 requestAnimationFrame 可以避免连续调用导致的相互干扰 |
案例
监听 scroll 函数
-
页面滚动事件 scroll 的监听函数,就很适合用这个 api ,推迟到下一次重新渲染;
const scrollHandler = () => { // todo }; $(window).on('scroll', function () { window.requestAnimationFrame(scrollHandler); })
-
平滑滚动到页面顶部;
const scrollToTop = () => { const c = document.documentElement.scrollTop || document.body.scrollTop; if (c > 0) { window.requestAnimationFrame(scrollToTop); window.scrollTo(0, c - c / 8); } } scrollToTop();
大量数据渲染
-
使用 setTimeout 实现
let ul = document.getElementById('container'); // 需要插入的容器 let total = 100000; // 插入十万条数据 let once = 20; // 一次插入 20 条 let page = total / once; // 总页数 let index = 0; // 每条记录的索引 // 循环加载数据 function loop(curTotal, curIndex) { if (curTotal <= 0) return false; // 每页多少条 let pageCount = Math.min(curTotal, once); setTimeout(() => { 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); }, 0); } loop(total, index);
-
使用 requestAnimationFrame 实现(推荐)
let ul = document.getElementById('container'); // 需要插入的容器 let total = 100000; // 插入十万条数据 let once = 20; // 一次插入 20 条 let page = total / once; // 总页数 let index = 0; // 每条记录的索引 // 循环加载数据 function loop(curTotal, curIndex) { if (curTotal <= 0) return false; let pageCount = Math.min(curTotal, once); // 每页多少条 window.requestAnimationFrame(function () { 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); }); } loop(total, index);
🏷 svg
上一篇