requestAnimationFrame与事件循环的关系解析
时间:2025-07-18 23:48:27 131浏览 收藏
本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《requestAnimationFrame 不属于事件循环,但与之紧密相关。它是一个由浏览器提供的 API,用于优化动画性能,确保在下一帧渲染之前执行回调函数。详细解释:事件循环(Event Loop) JavaScript 是单线程的,通过事件循环来处理异步操作。事件循环的主要职责是管理任务队列(如宏任务和微任务),并按顺序执行它们。requestAnimationFrame 的工作原理requestAnimationFrame 是一个浏览器 API,不是 JavaScript 语言本身的特性。它的作用是告诉浏览器:“我打算在下一次重绘之前执行这个函数”。 浏览器会在每次重绘(通常是 60Hz,即每秒 60 次)前调用注册的回调函数。它会自动根据屏幕刷新率调整执行频率,避免不必要的计算。与事件循环的关系 虽然 requestAnimationFrame 是一个异步方法,但它并不是事件循环的一部分。它是由浏览器调度的,优先级高于普通的 setTimeout 或 setInterval,并且通常在事件循环的“渲染阶段”被触发。为什么它不算是事件循环的一部分? 事件循环处理的是 JavaScript 的任务(如 setTimeout、Promise 等)。requestAnimationFrame 是》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~
requestAnimationFrame(rAF)不属于宏任务或微任务队列,而是浏览器专为动画优化的特殊调度机制。1. 它与屏幕刷新率同步,在每次重绘前执行回调;2. 回调被加入浏览器维护的“动画帧回调列表”,在JavaScript主线程空闲、微任务完成后执行;3. 若浏览器跳过某帧渲染,对应rAF回调也会被跳过,确保按需执行;4. 优于setTimeout之处在于同步渲染周期、节能省电、批处理提升性能;5. 回调中适合进行样式修改、Canvas/WebGL绘制及状态更新;6. 注意避免长时间计算阻塞主线程、减少布局抖动、需递归调用以维持动画循环并适时取消。
requestAnimationFrame
(rAF) 从严格意义上讲,它不直接“属于”JavaScript事件循环中我们常说的宏任务(macro-task)或微任务(micro-task)队列。它有自己一套独特的调度机制,与浏览器的渲染周期紧密同步。你可以把它理解为浏览器提供的一个专门用于动画和视觉更新的“特殊通道”或“渲染前回调队列”,这个队列在每次浏览器准备绘制新帧之前被执行。它确实是整个浏览器事件处理和渲染流程不可或缺的一部分,但其优先级和执行时机与常规的事件队列有所不同。

解决方案
要理解requestAnimationFrame
在浏览器工作流中的位置,我们需要先回顾一下事件循环的基本概念。JavaScript的事件循环负责调度任务,包括执行脚本、处理用户交互、网络请求以及定时器等。宏任务(如setTimeout
, setInterval
, I/O, UI rendering)和微任务(如Promise.then
, MutationObserver
)是其核心组成。
requestAnimationFrame
的独特之处在于,它被设计成与浏览器的屏幕刷新率同步。当你的代码调用requestAnimationFrame(callback)
时,这个callback
函数并不会立即被放入宏任务或微任务队列。相反,它会被加入到一个由浏览器维护的“动画帧回调列表”中。浏览器在每次屏幕刷新前(通常是每16.6毫秒,对应60fps),会执行一系列步骤,其中就包括执行这个动画帧回调列表中的所有函数,然后进行样式计算、布局、绘制,最后呈现到屏幕上。

这意味着,rAF
的回调是在浏览器下一次重绘之前执行的。它发生在所有常规的宏任务和微任务之后,但在浏览器真正进行页面渲染之前。这种紧密的同步性是其核心价值所在,它确保了动画的流畅性和效率,避免了不必要的重绘和“画面撕裂”现象。它不是一个标准的事件,而更像一个渲染优化机制。
requestAnimationFrame是如何被浏览器调度的?
requestAnimationFrame
的调度机制确实很巧妙,它不是简单地扔进一个队列就完事了。当你在代码里调用window.requestAnimationFrame(myAnimationFunction)
时,myAnimationFunction
这个回调函数并不会像setTimeout
的回调那样被放入一个普通的任务队列。浏览器内部会维护一个专门的“动画帧回调列表”。

每次浏览器要准备下一帧的渲染时(这通常与显示器的刷新率同步,比如每秒60次),它会经历一个固定的流程。在这个流程中,有一个明确的阶段是专门用来处理requestAnimationFrame
回调的。具体来说,它通常发生在JavaScript主线程的当前任务执行完毕、所有微任务也处理完毕之后,但在浏览器计算样式、布局(reflow)、绘制(repaint)之前。
如果浏览器因为某些原因(比如CPU繁忙、页面被最小化或切换到后台)决定跳过某一帧的渲染,那么原本计划在那一帧执行的requestAnimationFrame
回调也会被跳过。这正是它的智能之处:它不会像setTimeout(fn, 16)
那样,即使浏览器来不及渲染也照样执行回调,从而可能导致动画卡顿或帧率不稳。rAF
是“按需”执行的,只有在浏览器准备好渲染新帧时,它才会执行你的动画代码,这极大地优化了性能和用户体验。
为什么使用requestAnimationFrame比setTimeout更适合动画?
这真的是一个老生常谈但又极其重要的问题。用setTimeout
来做动画,就像是蒙着眼睛开车,你不知道路况如何,只能凭感觉踩油门。而requestAnimationFrame
则像是有了GPS和路况实时更新,它能让你与浏览器的渲染节奏完美同步。
首先,最关键的一点是同步性。显示器有固定的刷新率(比如60Hz),这意味着它每秒刷新60次。如果你用setTimeout(callback, 16)
来模拟60fps,这个16
毫秒只是一个最小延迟,实际执行时间可能更长。更要命的是,你的回调执行时机和浏览器渲染周期的起始点很可能错位,这会导致“画面撕裂”(tearing)或者动画卡顿(jank)。rAF
则不然,它会告诉浏览器:“嘿,我这里有段动画代码,你下次要画新画面的时候,麻烦在画之前帮我执行一下。”这样,你的动画更新总是在浏览器重绘之前完成,确保每一帧都是连贯、完整的。
其次是浏览器优化。浏览器对rAF
有很强的控制权。当你的页面不在活动状态(比如标签页被切换到后台,或者浏览器窗口被最小化)时,浏览器会暂停或降低rAF
回调的执行频率,这能显著节省CPU和电池资源。而setTimeout
可不管这些,它会傻乎乎地按照你设定的时间间隔在后台持续运行,白白消耗资源。
再者是性能优势。rAF
的回调通常会批处理执行。这意味着,即使你有很多个rAF
回调,它们也会在同一帧内、在一次布局和绘制操作前全部执行完毕。这避免了多次不必要的DOM操作和回流/重绘,从而提升了整体性能。而setTimeout
则可能导致多次独立的DOM更新,每次更新都可能触发一次布局和绘制,造成“布局抖动”(layout thrashing),严重影响性能。
requestAnimationFrame的回调函数中可以执行哪些操作,需要注意什么?
在requestAnimationFrame
的回调函数里,核心目的就是进行与视觉更新相关的操作,确保这些操作能在下一帧被渲染出来。
你可以放心地进行DOM元素的样式修改,比如改变transform
属性(位移、旋转、缩放)、opacity
、width
、height
等等。这些都是浏览器优化渲染的关键属性。如果你在进行复杂的动画,比如粒子效果或者游戏,那么在rAF
回调里进行Canvas绘图或者WebGL渲染是最佳实践。此外,更新动画的状态变量(比如元素当前的位置、速度、颜色值等)也是常见的操作。
不过,在使用rAF
时,有几点需要特别注意,这关系到动画的流畅性和应用的整体性能:
首先,避免在回调中执行耗时过长的同步计算。rAF
的回调函数依然运行在JavaScript主线程上。如果你的回调函数执行时间超过了浏览器刷新一帧的时间(例如,对于60fps的显示器,这个时间是约16.6毫秒),那么就会导致“掉帧”(frame drop),用户会感觉到动画卡顿。所以,任何复杂的计算、大量数据处理都应该考虑放到Web Workers中去,避免阻塞主线程。
其次,注意DOM操作的效率,避免“布局抖动”。虽然rAF
会批处理DOM更新,但如果你在同一个回调中,频繁地先读取DOM的计算样式(比如element.offsetWidth
),然后又修改DOM样式,再读取,再修改,这会导致浏览器反复进行布局计算,严重拖慢渲染速度。最佳实践是,先集中读取所有需要的DOM属性,然后集中进行所有DOM写入操作。
最后,要记得递归调用requestAnimationFrame
来创建动画循环。rAF
只执行一次回调。要实现连续动画,你需要在每次回调函数结束前再次调用requestAnimationFrame
,传入自身或其他下一个动画步骤的回调。同时,当动画不再需要时,务必使用cancelAnimationFrame
来停止循环,避免不必要的资源消耗。
以上就是《requestAnimationFrame与事件循环的关系解析》的详细内容,更多关于的资料请关注golang学习网公众号!
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
477 收藏
-
461 收藏
-
297 收藏
-
254 收藏
-
261 收藏
-
369 收藏
-
493 收藏
-
239 收藏
-
449 收藏
-
103 收藏
-
216 收藏
-
497 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习