全面理解 requestAnimationFrame

您所在的位置:网站首页 took什么 全面理解 requestAnimationFrame

全面理解 requestAnimationFrame

#全面理解 requestAnimationFrame| 来源: 网络整理| 查看: 265

一、requestAnimationFrame 的定义

官方的定义:

image.png

requestAnimationFrame 即请求动画帧,它是一个浏览器的宏任务。简单的说,这个api主要是用来做动画的。

二、关于前端动画的两个问题 1. 前端动画方案有哪些?

css动画

transition:过渡动画 animation:直接动画(搭配@keyframes)

js动画

setInterval或setTimeout定时器(比如不停地更改dom元素的位置,使其运动起来) canvas动画,搭配js中的定时器去运动起来(canvas只是一个画笔,然后我们通过定时器会使用这个画笔去画画-动画) requestAnimationFrame动画(js动画中的较好方案) 2. 为何要使用这个新的api来做动画?

在工作中,做动画最优的方案无疑是css动画,但是某些特定场景下,css动画无法实现我们所需要的需求。这时,我们就要考虑使用js去做动画了,canvas动画的本质也是定时器动画。使用定时器动画干活,实际上是可以的,但是存在一个最大的问题,就是动画会抖动,体验效果不是非常好。

而使用requestAnimationFrame去做动画,就不会出现抖动的现象。

这里笔者写一个demo动画(分别是上述两种方式实现dom元素向右平移)给大家看一下,就知道具体的区别。我们先看一下效果图:

111.gif

由于gif录制软件的问题,看着都有点卡。实际上,大家把下方代码复制一份跑起来看的话,会发现定时器动画在微微颤抖,而requestAnimationFrame动画却稳如老狗

requestAnimationFrame_yyds body { box-sizing: border-box; background-color: #ccc; } .box1, .box2 { position: absolute; width: 160px; height: 160px; line-height: 160px; text-align: center; color: #fff; font-size: 13px; } .box1 { top: 40px; background: red; } .box2 { top: 210px; background: green; } 👉 let's go! 定时器动画 请求动画帧 // 动画思路:不断修改dom元素的left值,使其运动起来(动画) let box1 = document.querySelector('.box1') let box2 = document.querySelector('.box2') // setInterval定时器方式 function setIntervalFn() { let timer = null box1.style.left = '0px' timer = setInterval(() => { let leftVal = parseInt(box1.style.left) if (leftVal >= 720) { clearInterval(timer) } else { box1.style.left = leftVal + 1 + 'px' } }, 17) } // requestAnimationFrame请求动画帧方式 function requestAnimationFrameFn() { let timer = null // 可注掉 box2.style.left = '0px' function callbackFn() { let leftVal = parseInt(box2.style.left) if (leftVal >= 720) { // 不再继续递归调用即可,就不会继续执行了,下面这个加不加都无所谓,因为影响不到 // cancelAnimationFrame取消请求动画帧,用的极少,看下,下文中的回到顶部组件 // 大家会发现并没有使用到这个api(这样写只是和clearInterval做一个对比) // 毕竟,正常情况下,requestAnimationFrame会自动停下来 cancelAnimationFrame(timer) // 可注掉(很少用到) } else { box2.style.left = leftVal + 1 + 'px' window.requestAnimationFrame(callbackFn) } } window.requestAnimationFrame(callbackFn) } // 动画绑定 let btn = document.querySelector('.btn') btn.addEventListener('click', () => { setIntervalFn() requestAnimationFrameFn() })

通过上述的例子,我们可以回答这个问题了:

面试官问:requestAnimationFrame比定时器好在哪里? 候选人答:好在比较稳定,动画不卡顿 面试官说:你回去等通知吧...

所以在这里,我们还要顺带延伸一下,为什么定时器会卡,而requestAnimationFrame不会卡。在说这个问题之前,我们先来看下requestAnimationFrame的语法规则。

三、requestAnimationFrame的语法规则

requestAnimationFrame和js中的setTimeout定时器函数基本一致,不过setTimeout可以自由设置间隔时间,而requestAnimationFrame的间隔时间是由浏览器自身决定的,大约是17毫秒左右

1.requestAnimationFrame我们可以在控制台输入window,然后展开查看其身上的属性,就能找到了,如下图:

xxx.png

2.由上图我们可以看到,requestAnimationFrame本质上是一个全局window对象上的一个属性函数,所以我们使用时,直接:window.requestAnimationFrame(callBack)即可。

3.和定时器一样其接收的参数callback也是一个函数,即下一次重绘之前更新动画帧所调用的函数,即在这个函数体中,我们可以写对应的逻辑代码(和定时器类似)。

4.requestAnimationFrame也有返回值,返回值是一个整数,主要是定时器的身份证标识,可以使用 window.cancelAnimationFrame()来取消回调函数执行,相当于定时器中的clearTimeout()。

5.二者也都是只执行一次,想要继续执行,做到类似setInterval的效果,需要写成递归的形式(上述案例中也提到了)

四、关于卡顿的问题 1. 为什么定时器会卡 我们在手机或者电脑显示屏上看东西时,显示屏会默默的不停地干活(刷新画面) 这个刷新值得是每秒钟刷新次数,普通显示器的刷新率约为60Hz(每秒刷新60次),高档的有75Hz、90Hz、120Hz、144Hz等等 刷新率次数越高,显示器显示的图像越清晰、越流畅、越丝滑 不刷新就是静态的画面,刷新比较低就是卡了,PPT的感觉 动画想要丝滑流畅,需要卡住时间点进行代码操作(代码语句赋值、浏览器重绘) 所以只需要每隔1000毫秒的60分之一(60HZ)即约为17毫秒,进行一次动画操作即可 只要卡住这个17毫秒,每隔17毫秒进行操作,就能确保动画丝滑 但是定时器的回调函数,会受到js的事件队列宏任务、微任务影响,可能设定的是17毫秒执行一次,但是实际上这次是17毫秒、下次21毫秒、再下次13毫秒执行,所以并不是严格的卡住了这个60HZ的时间 没有在合适的时间点操作,就会出现:类似这样的情况:变、不变、不变、变、不变... 于是就出现了,绘制不及时的情况,就会有抖动的出现(以上述案例,位置和时间没有线性对应更新变化导致看起来抖动) 2. 为何requestAnimationFrame不会卡

setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔,参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。

requestAnimationFrame能够做到,精准严格的卡住显示器刷新的时间,比如普通显示器60HZ它会自动对应17ms执行一次,高级显示器120HZ,它会自动对应9ms执行一次。当然requestAnimationFrame只会执行一次,想要使其多次执行,要写成递归的形式。

所以,这就是requestAnimationFrame的好处,window.requestAnimationFrame这个api就是解决了定时器不精准的问题的。

五、requestAnimationFrame 的应用场景

比如:回到顶部组件,就是使用requestAnimationFrame实现的。

效果图:

333.gif

代码:

export default { name: "myBack", props: { bottom: { type: Number, default: 72, }, right: { type: Number, default: 72, }, // 回到顶部出现的滚动高度位置 showHeight: { type: Number, default: 240, }, // 拥有滚动条的那个dom元素的id或者class,用于下方选中操作更改滚动条滚动距离 scrollBarDom: String, }, data() { return { visible: false, scrollDom: null, }; }, mounted() { if (document.querySelector(this.scrollBarDom)) { this.scrollDom = document.querySelector(this.scrollBarDom); // 不用给window绑定监听滚动事件,给对应滚动条元素绑定即可 this.scrollDom.addEventListener("scroll", this.isShowGoToTop, true); } }, beforeDestroy() { // 最后要解除监听滚动事件 this.scrollDom.removeEventListener("scroll", this.isShowGoToTop, true); }, methods: { isShowGoToTop() { // 获取滚动的元素,即有滚动条的那个元素 if (this.scrollDom.scrollTop > 20) { this.visible = true; } else { this.visible = false; } }, goToTop() { // 获取滚动的元素,即有滚动条的那个元素 let scrollDom = document.querySelector(this.scrollBarDom); // 获取垂直滚动的距离,看看滚动了多少了,然后不断地修改滚动距离直至为0 let scrollDistance = scrollDom.scrollTop; /** * window.requestAnimationFrame兼容性已经可以了,正常都有的 * */ if (window.requestAnimationFrame) { let fun = () => { scrollDom.scrollTop = scrollDistance -= 36; if (scrollDistance > 0) { window.requestAnimationFrame(fun); // 只执行一次,想多次执行需要再调用 } else { scrollDom.scrollTop = 0; } }; window.requestAnimationFrame(fun); return; } /** * 没有requestAnimationFrame的话,就用定时器去更改滚动条距离,使之滚动 * */ let timer2 = setInterval(() => { scrollDom.scrollTop = scrollDistance -= 36; if (scrollDistance


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3