问题描述
问题来源于一个用canvas的getContext('2d')对象的小DEMO,里面用了requestAnimationFrame()这个API
在MDN里面,他解释为:
该方法告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画,当你需要更新屏幕画面时就可以调用此方法。在浏览器下次重绘前执行回调函数。回调的次数通常是每秒60次,但大多数浏览器通常匹配 W3C 所建议的刷新频率。
下面有几点的问题
问题是:
1.这个函数可不可以理解为就是一个 1000/60 的定时器呢?
2.如果不是,那是不是相当于一个监听器,监听重绘这个事件然后执行吗?那么我之前写的每一帧都会触发这个函数吗?
3.重绘的理解。这里的重绘我理解是画面改变了,这样的话,比如我用canvas画了一个圆或者一个窗口大小的填充矩形,算重绘吗?
要理解
requestAnimationFrame
,你得先要理解event loop
事件循环,看下图简单解释下图片,事件循环就是进程会一直在中间转圈
当没有别的事情的时候,会在中间循环做自己的事
当接到通知有任务时,任务开关会打开,会进到任务列表里执行任务
当有渲染需求时,渲染开关会打开,进入渲染步骤
如此反复,整个过程是同步的
下面是解答
requestAnimationFrame
如图所示,在渲染的前面,也就是说在requestAnimationFrame
会在渲染之前执行好,每次的执行时间是在渲染之前,而进程是以同步的方式进行的,所以并没有办法确定时间就会是 1000/60 ,当进程在进入渲染之前有别的阻塞,那直到阻塞结束才会进到渲染里面,比如用了alert()
或者任务列表里有长时间的计算.使用
跟
setTimeout
比起来,requestAnimationFrame
会在每次渲染之前执行,那么就可以把跟渲染相关的操作放在这里,比如像盒子的移动,要根据滚动条位置来计算盒子的位置等等,这样可以保持和渲染的频率同步,就是不会丢帧.纵享丝滑体验.之前我也是这么认为的,只是理想很性感,现实很骨感.
最开始的时候我查了下requestAnimationFrame 兼容性,发现兼容性真心不错的,除了一些老古董都能支持的.
后来有发现的一个问题,支持了是不错,但并不是所有
requestAnimationFrame
都是在渲染之前的,有的是在渲染之后的,如下图这就尴尬了,如果只考虑
chrome
和firefox
的话,使用requestAnimationFrame
体验会很好,如果要兼容更多就需要考虑更多的兼容方案了.更多详情,可以参考第一个链接里的视频,讲的很生动
补充(回复评论)
我的意思是整个事件循环的过程是同步的,也就是说如果还在执行栈当中,接到任务通知,打开任务开关,在执行空闲的时间进到任务列表里面,这是同步,而不会直接跳到任务列表,渲染也是一样.
你说的微任务和宏任务任务的异步,是指他们具体的执行函数是异步的,但是去调用他们的过程是同步的.
比如下面这段代码
当执行到
setTimeout
的时候,会往任务列表里面添加一条任务,这个时候并不会执行setTimeout
的回调,会继续执行console.log(a)
,这个时候执行栈为空,当符合setTimeout
的触发条件时,也就是到1000ms
时,进程会进到任务列表去触发任务.如果把后面一个
while (true) {}
代码注释去掉,那么永远也不会执行setTimeout
里面的回调,因为进程一直在循环里面,不会去触发setTimeout
的回调.微任务和宏任务任务的异步,体现在当进程触发了回调函数之后,这个回调函数的执行不会去阻塞主进程,比如把第一个while (true) {}
去掉,当进程去触发回调的时候,并不会因为回调里面的无限循环而阻塞,而是会继续往前执行.然后在1500ms
的时候触发第二个任务,输出setTimeout 1500
,而console.log(a)
永远不会输出.这里确实不对,是我太粗心了.
参考