聊聊使用 JavaScript 做动画出现卡顿的原因
前段时间在使用 JavaScript 做动画的时候发现做出来的动画会出现卡顿的现象,今天我们主要就来聊一下卡顿的原因以及如何解决这个问题。
使用定时器实现动画出现卡顿的原因
- 主要原因是浏览器无法确定定时器的回调函数的执行时机。以
setInterval
为例,当一个setInterval
定时器被创建后,它的回调任务会被放到异步队列,只有当同步任务执行完成后,浏览器才会检查异步队列中是否有需要执行的异步任务,如果有,就取出执行,这样会使任务的实际执行时机比所设定的延迟时间要晚一些。
这个问题跟浏览器的事件循环机制有关,JavaScript 引擎在解析执行我们的代码的时候,遇到定时器,会调用浏览器 API,让定时器去进行倒计时,此时并不阻塞同步代码的执行,当定时器倒计时完毕,定时器回调会被放入宏任务队列等待执行。
在这个过程中问题就来了,如果说同步代码的执行需要50ms,而定时器设置的定时只有20ms,那么由于事件循环的机制,还是要等待同步任务执行完整之后再来执行微任务队列中的定时器回调,而这中间,又相隔了30ms,在这30ms的过程中,定时器的回调一直处于 pendding 的状态。如果定时器中是动画相关的操作,那也需要在预期的时间上多等待50ms。
画了张图,希望能帮助大家理解(如果不能帮助大家理解,那么请忽略这张图……)
- 屏幕分辨率和尺寸也会影响刷新频率,不同设备的屏幕绘制频率可能会有所不同,而
setInterval
只能设置一个固定的时间间隔,这个间隔时间不一定与屏幕的刷新时间同步,所以就可能会导致动画出现随机丢帧的问题。
这里有两个点,一个是显示器的刷新频率,另一个是定时器的时间间隔。
一般显示器刷新频率都是60Hz,这基本上意味着每秒需要重绘60次。大多数浏览器都会限制重绘的频率,使其不超过显示器的刷新频率。因为超过刷新频率,用户也感知不到,白白浪费性能。
因此,实现平滑动画最佳的重绘间隔为1000ms/60,大约17毫秒。以这个速度重绘,可以实现最平滑的动画效果。因为这已经是浏览器的重绘频率的极限了。
知道何时绘制下一帧,是创造平滑动画的关键。直到几年前,都没有确切保证让浏览器在何时把下一帧绘制出来的方法。随着 <canvas>
和 HTML5
游戏的兴起,开发者发现 setInterval
和 setTimeout
的不精确是个大问题,而浏览器自身的计时器也存在着精度不足毫秒的问题。
以下是几个浏览器计时器的精度情况:
- IE8 以及之前的版本计时器精度为 15.625ms;
- IE9 及之后的版计时器精度为 4ms;
- FireFox 和 Safari 的计时器精度约为 10ms;
- Chrome 的计时器精度为 4ms。
以 Chrome 为例,它的计时器精度为 4ms,这意味着 0~4 之间的任何值最终要么是 0,要么是4;不可能是别的值。因此,即使将浏览器的时间间隔设置为最优,也免不了只能得到相近似的结果。
对于 JavaScript 来说,它不知道浏览器会在何时发生重绘。因此,我们通过定时器做动画的时候,在定时器中控制动画的代码已经执行完成的情况下,动画效果并不一定会立马生效,因为此时浏览器可能还处在等待下一次重绘的过程中,当下一次重绘完成,动画效果才能在浏览器窗口中显示出来。
而由于浏览器计时器时间差的问题,会导致定时器的计时并不一定是我们设置的 17 ms,而是在多个时间点内反复横跳,也因此才出现使用定时器做动画的时候动画抖动的问题,在复杂的动画中,这种问题尤为明显。
在这样的环境下,今天的主角 requestAnimationFrame
应运而生!
requestAnimationFrame
的前世今生
Mozilla 的 Robert O’Callahan 一直在思考这个问题,并且提出了一个独特的解决方案。他指出,浏览器知道 CSS 过渡和动画应该什么时候开始,并且能够计算出正确的时间间隔,到时间就去刷新用户界面。
但是对于 JavaScript 而言,浏览器并不知道动画什么时候开始。他给出的方案是创造一个名为 MozRequestAnimationFrame
的新方法,以此来通知浏览器某些 JavaScript 代码要执行动画了。这样浏览器就可以在运行某些代码后进行适当的优化。
目前,所有的浏览器都支持这个方法不带前缀的版本,也就是现在用到的 requestAnimationFrame
。
requestAnimationFrame VS setInterval
这里就不再过多的介绍 requestAnimationFrame
的详细用法了,它的用法并不复杂。
与定时器不同的是,requestAnimationFrame
只会在被调用的时候执行一次动画,而不会连续执行。如果想做连续的动画,则可以通过递归来实现对 requestAnimationFrame
的连续调用。
接下来通过一个 demo 来对比一下使用 requestAnimationFrame
和 setInterval
两者做出来的动画效果之间的差异。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><style>.square1,.square2 {position: absolute;width: 100px;height: 100px;}.square1 {top: 40px;background: red;}.square2 {top: 150px;background: blue;}</style><body><div class="container"><div class="square1"></div><div class="square2"></div><button class="btn">开始!</button></div><script>const square1El = document.querySelector('.square1')const square2El = document.querySelector('.square2')// 定时器版function squareMove() {let timer = nullsquare1El.style.left = '0px'timer = setInterval(() => {const squareLeft = parseInt(square1El.style.left)if (squareLeft >= 500) return clearInterval(timer)square1El.style.left = squareLeft + 1 + 'px'}, 17)}// requestAnimationFrame 版function squareMove2() {let timer = nullsquare2El.style.left = '0px'function updateAnimation() {const squareLeft = parseInt(square2El.style.left)if (squareLeft >= 500) return cancelAnimationFrame(timer)square2El.style.left = squareLeft + 1 + 'px'window.requestAnimationFrame(updateAnimation)}window.requestAnimationFrame(updateAnimation)}document.querySelector('.btn').addEventListener('click', () => {squareMove()squareMove2()})</script></body>
</html>
在页面中画了两个正方形,当点击按钮的时候方块开始运动,红色方块是使用 setInterval
实现的动画,蓝色方块使用的是 requestAnimationFrame
。
接下来看一下实现的效果。
在生成gif的时候视频被压缩了,但是还是能看到红色的方块在开始运动的时候有明显的抖动,而蓝色的方块则比较丝滑。
实际上,requestAnimationFrame
的回调函数可以接收一个参数,这个参数是一个 DOMHightResTimeStamp
的实例(比如:performance.now()的返回值),用来表示下一次重绘的时间。这一点非常重要,requestAnimationFrame
实际上是把重绘任务安排在了未来的一个已知的时间点上,而且通过这个参数来告诉开发者。
类似于 setInterval
的清除方法 clearInterval
,requestAnimationFrame
也有对应的取消重绘的方法 cancelAnimationFrame
,用法也跟 clearInterval
非常类似,在每次调用 requestAnimationFrame
的时候,都会返回一个id,cancelAnimationFrame
就是通过这个id去取消对应的 requestAnimationFrame
。
看到这里,大家应该对 setInterval
和 requestAnimationFrame
都有了更深的了解,以后使用 JavaScript 做动画还是以 requestAnimationFrame
为主。
希望讲解的内容能对大家有所帮助~
如果有更多其他想了解的内容欢迎关注 大话前端,给我留言,我们一起交流讨论~
参考资料
[1]《JavaScript高级程序设计(第四版)》第18章第1节。
聊聊使用 JavaScript 做动画出现卡顿的原因相关推荐
- ipad更新9.0系统更新服务器,iPhone/iPad升级iOS9出现卡顿的原因及解决办法
iOS 9正式版目前已经正式推送了,不过在一部分热火朝天升级的时候,有一部分人还在处在观望的状态.现在有很多升级之后的用户反馈说自己的iPhone或者iPad等苹果设备在升级到iOS 9系统会出现暂时 ...
- App卡顿的原因及解决方案
卡顿现象对我们来说是一个很烦恼的问题,也会给用户使用过程中带来很直观的不良感受,主要是由代码.内存不足等问题引起的常规卡顿和ANR异常,我们可以使用"友盟+u-apm"这款工具进行 ...
- 页面卡顿的原因及排查
页面卡顿的原因.排查及解决方案 一.渲染不及时,页面掉帧 1>网络请求太多,请求返回的数据比较慢 接口返回慢的话,后端做些优化:前端适当做些缓存,减少不必要的重复的请求 可以从调试工具中的Net ...
- 计算机各种办公软件都很卡,垃圾佬软件推荐 篇四:百分之99高性能电脑卡顿的原因找到了,快看看你有没有中招...
垃圾佬软件推荐 篇四:百分之99高性能电脑卡顿的原因找到了,快看看你有没有中招 2021-01-06 18:38:04 8点赞 38收藏 16评论 创作立场声明:原创 1 2345 头号流氓软件 几乎 ...
- C语言编译器卡顿,Objective - C 性能优化(一)卡顿产生原因及避免
关于性能优化之卡顿产生原因,就不得不先理解屏幕成像原理 一.CPU .GPU 在屏幕成像过程中,CPU和GPU起着至关重要的作用 CPU (中央处理器) 对象的创建和销毁.对象属性的调整.布局计算.文 ...
- w10计算机运行特别卡,电脑卡是什么问题?导致Win10正式版卡顿的原因及解决方法...
在使用电脑的时候经常会遇到各种难题,例如有些伙伴们说自己的电脑Win10 1909系统很卡怎么解决的现象,像遇到这样的问题该如何解决呢?其实非常简单,对于电脑卡是什么问题?导致Win10正式版卡顿的原 ...
- 为什么荒野行动服务器信息卡了,荒野行动画面卡顿的原因是什么?荒野行动画面卡顿的快速有效解决方法...
荒野行动画面卡顿的原因是什么?荒野行动画面卡顿怎么办?画面卡顿了要怎么解决?荒野行动手游对手机配置要求还是很高的,很多玩家在游戏的过程中会遇到画面卡顿的问题,那么遇到这种问题要怎么办呢?一起来看看吧! ...
- iOS Xcode启动模拟器之后电脑卡顿的原因
本人遇到过启动Xcode的模拟器之后整个电脑变得非常卡顿的原因!因为在项目中不小心写了个死循环,对自己也是无语之至!
- 风暴英雄出现服务器未响应,《风暴英雄》游戏卡顿的原因分析及解决办法
<风暴英雄>游戏卡顿的原因分析及解决办法 2014-11-02 15:14:47来源:游戏下载编辑:阿狸桃子评论(0) <风暴英雄>内测一周以来,有不少玩家反映玩起来比较卡.那 ...
最新文章
- 专访博世王红星:大数据和AI将是中国制造业升级新动力
- MindSpore布道师队伍招募开始,助力小白成为大牛!
- [YTU]_2443( C++习题 复数类--重载运算符3+)
- 03-iOS蓝牙架构搭建
- 如何用ARP欺骗来嗅探主机流量
- 统计一行文本的单词个数_word操作技巧:不同情况的文字统计方法
- 数学家的亿万商业王国:创建“验证码”和“多邻国”,20岁就被盖茨挖去微软...
- 虽然现在有可以去码的软件了,可视频是如何自动跟踪打码的?
- 【连载】【FPGA黑金开发板】Verilog HDL那些事儿--数码管电路驱动(八)
- 收藏~10年软件测试人员的工作方法进阶汇总
- SRS之SrsRtmpConn::publishing详解
- spark配置IntelliJ开发环境详解
- Python 网络爬虫第二讲
- MATLAB求图像的均值、中值、众数、方差、协方差、相关系数
- java程序将asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv,mpeg,mpe,wmv9,rm,rmvb转MP4
- OpenGL的环境光、点光源、聚光灯、方向光、材质的定义。
- 新元宇宙奇科幻小说原创作品每周连载地球人奇游天球记第六回冬奥登月
- FILecoin 将重大战略升级,FIL 或将引导商业数据
- node-sass 安装失败问题 2022最新版
- 高级编程中C语言属于,c语言高级编程
热门文章
- 云场景实践研究第15期:花粉儿APP
- 微信公众号:微信H5页面JSAPI充值
- CSAPP:MallocLab
- 论文精读:基于深度学习的表面缺陷检测方法综述
- Android 8.1 【FriendlyARM】温度压力传感器-BMP180 驱动开发
- mysql命令行界面出现问题,MySQL命令行界面中出现字符错误提示的原因及解决方法...
- 数据库领域正在发生巨变,从 TiDB 5.0 发布会看未来的数据库发展趋势
- Postgresql中的large object
- repeater的使用方法详解
- 软件为什么应该是自由的 Richard Stallman(理查德·斯多尔曼) 著