文章目录
近期文章:
- Nginx Upstream了解一下
- 实现篇:一文搞懂Promise是如何实现的
- 实现篇:如何手动实现JSON.parse
- 实现篇:如何亲手定制实现JSON.stringify
- 一文搞懂 Markdown 文档规则
经常做网页动画的童鞋,对requestAnimationFrame
应该是很熟悉的,都说比setTimeout好,常常不知道具体好在哪里,今天这篇就从底层渲染开始讲清楚。随着人们对丰富、交互式 Web 体验的需求不断增长,这给 Web 前端开发带来了越来越大的压力,希望能提供流畅高效的动画效果。虽然传统的 JavaScript 定时机制(如 setInterval
和 setTimeout
)一直被用于此目的,但它们在提供最佳性能方面往往有所欠缺,导致动画卡顿和资源利用效率低下。requestAnimationFrame
作为现代解决方案应运而生,它为 Web 动画提供了一种浏览器优化的方法。
1 理解浏览器渲染管线
在理解 requestAnimationFrame
的性能优势之前,首先必须理解浏览器渲染管线,即浏览器将 HTML、CSS 和 JavaScript 代码转换为屏幕上的可视输出所执行的一系列步骤。此管线涉及几个关键阶段,每个阶段都对最终渲染的帧做出贡献。
解析
该过程始于HTML 解析和 DOM 构建。当用户导航到网页时,浏览器会收到 HTML 文档作为文本文件。然后,HTML 解析器将此文本转换为文档对象模型 (DOM),这是一个表示文档内容和组织的树状结构。此解析过程是增量的,这意味着浏览器可以在接收 HTML 时开始构建 DOM。重要的是,JavaScript 也可以与 DOM 交互并修改 DOM,从而导致页面结构的动态变化。浏览器还会将任何相关的 CSS 解析为 CSS 对象模型 (CSSOM)。与 DOM 构建不同,CSSOM 构建不是增量的;浏览器通常会阻止渲染,直到所有 CSS 都下载并处理完毕,因为 CSS 规则可以级联和覆盖,因此需要在渲染开始之前完全理解样式。
构建渲染树
一旦 DOM 和 CSSOM 都构建完成,浏览器就会继续构建渲染树。此树将 DOM 中的内容与 CSSOM 中的样式信息相结合,但它只包含页面上可见的元素。<head>
部分中的元素(通常)以及 display: none;
的元素都将从渲染树中排除。渲染树本质上表示将要绘制的文档的可视结构。
布局
有了渲染树之后,浏览器会执行布局,也称为重排 (reflow)。在此阶段,浏览器会计算渲染树中每个元素在视口内的精确位置和大小。布局是一个复杂且计算密集的过程,尤其是在处理大量 DOM 节点时,因为一个元素的尺寸和位置会影响其他元素。任何影响渲染树几何形状的更改,例如宽度、高度或位置的更改,都将触发布局。
绘制
布局之后,浏览器进入绘制阶段,在此阶段,它会遍历渲染树并为每个可视元素发出绘制指令。这包括用颜色填充像素、渲染文本、绘制图像、边框和阴影,通常在多个图层上进行。基于 Chromium 的浏览器使用 Skia 图形库来促进此绘制过程,这可能涉及 GPU 以实现硬件加速。重要的是,对于后续帧,只会重新绘制自上次绘制以来屏幕上发生变化的区域,从而优化了该过程。
合成
渲染管线的最后阶段是合成。在这里,浏览器将各种绘制的图层组合起来,以生成网页的最终视觉表示。此步骤会考虑元素的堆叠顺序 (z-index)、透明度和混合模式。合成也可以通过 GPU 进行硬件加速,从而显著提高性能,尤其是在涉及变换和不透明度的动画方面。
在 Chromium 中,渲染过程涉及一个多进程架构,包括浏览器进程、渲染器进程和 Viz 进程。渲染器进程负责特定选项卡的渲染管线,它包括一个合成器线程,该线程在处理滚动和动画方面起着至关重要的作用,通常将这些任务从主线程卸载以获得更好的性能。理解此管线对于理解 requestAnimationFrame
如何通过与浏览器的内部渲染机制协同工作来优化动画过程至关重要。
2 使用 setInterval
和 setTimeout
进行动画的性能限制
虽然 setInterval
和 setTimeout
提供了一种简单的方法来以特定的时间间隔或延迟执行 JavaScript 代码,但它们固有的特性在使用于动画时可能会导致严重的性能问题。这些限制源于它们缺乏对浏览器渲染管线的感知以及屏幕更新的时序。
function hello(name) {
console.log(`Hello, ${
name}!