前言
当我们的JavaScript的学习到达一定的深度时,我们在完成项目的功能实现后应该更多的关注项目的性能优化,这里我将带大家揭开性能优化之路之"浏览器底层渲染原理"。
深入了解浏览器底层渲染原理,可以对我们性能优化的理解更加深刻,从根源上识别并解决性能问题,提升产品体验。
名词介绍
-
DOM (Document Object Model)
: HTML文档被解析后形成的树状结构,代表了文档中的所有节点(如元素、文本等)以及它们之间的关系。 -
CSSOM (CSS Object Model)
: CSS规则被解析后的树状结构,表示了样式信息以及它们的优先级和继承关系。 -
Render Tree (渲染树)
(也称Layout Tree(布局s树)
): 结合DOM和CSSOM构建的树形结构,包含了需要被渲染到屏幕上的元素及其样式信息。不包括display: none的元素和head中的元素。Reflow发生的阶段,是计算渲染树中每个节点的几何信息(大小和位置)的过程。浏览器根据这些信息决定元素在页面上的确切排布。 -
分层合成(Layer Composition)
: 在一些现代浏览器中,页面的不同部分可以独立绘制并作为图层存在,之后这些图层被合成到一起形成最终的屏幕图像。这有助于高效更新页面的部分区域。 -
Painting (绘制)
: 将布局阶段计算好的各个元素按照颜色、背景、边框等样式绘制到屏幕上。这是通过UI后端层完成的。
渲染引擎是怎么工作的?
有没有想过一个问题:从输入url 到 看到页面浏览器渲染经历了什么过程?
也不只是url到页面响应存在浏览器渲染,而是你的每一步浏览器操作都会发送浏览器渲染。由于本文不讨论HTTP请求所有我们直接从返回HTML文档报文来聊。
当我们输入一段URL网址后,浏览器从服务器获取到返回的 HTML文档,如果页面包含CSS、JavaScript、图片等外部资源,响应头中还会包含这些资源的URL
渲染引擎工作
1. 解析 HTML。
解析过程中遇到 CSS 解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。
2. css 解析
当主线程解析到link
链接,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML。同时异步加载CSS文件。这样做可以确保HTML结构能够尽快被处理和显示,从而提高页面的加载速度。这也是CSS 不会阻塞 HTML 解析的根本原因。
但是,虽然CSS加载不会阻止HTML的解析,但它会影响页面的渲染过程。在CSSOM构建完成之前,浏览器通常会延迟渲染(也就是所谓的"渲染阻塞"),因为浏览器需要CSS信息来计算最终的布局和样式。这意味着,即使HTML已经解析完成,页面内容也可能不会立即显示,直到相关的CSS文件被下载和解析完毕,以便浏览器可以正确渲染带有样式的内容。
所以css会阻塞的吗? 会阻塞的 在渲染引擎工作时候阻塞(等待css解析完成才能进行下一步)
3. 计算样式(Computed Style)
这个阶段,主线程会遍历得到的 DOM 树和CSSOM树,依次为树中的每个节点计算出它最终的样式。在这一过程中,很多预设值会变成绝对值,比如red
会变成rgb(255,0,0)
;相对单位会变成绝对单位,比如em
会变成px
。 这一步完成后,会得到一棵带有样式的 DOM 树。
4. 生成渲染树(Render Treee)
阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。例如节点的宽高、相对包含块的位置。 渲染树也称布局树Layout Tree
,但是严格来说,布局树是在渲染树之后的,布局树确定的DOM节点所在的视图的位置。
大部分时候,DOM 树和布局树并非一一对应。
比如display:none
的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树中。还有匿名行盒、匿名块盒等等都会导致 DOM 树和布局树无法一一对应。
5. 图层创建(Layerization)
在这个阶段,浏览器会分析Render Tree
并决定哪些部分应该被分配到单独的图层。这样做主要是为了优化渲染性能,尤其是对于那些可能需要进行独立变换(如平移、旋转、缩放)或者具有复杂动画效果的元素。分层允许浏览器更高效地更新界面,因为它只需要重绘发生变化的图层,而不是整个页面。常见的触发新图层创建的情况包括使用position: fixed;
或position: absolute;
定位的元素,使用3D变换的元素,以及具有透明度变化的元素等。
浏览器在决定分层时会力求平衡,既要利用分层带来的性能优势,又要避免因图层数量过多而可能导致的反向效果。过多的图层可能会增加内存消耗,并且由于每一层都需要单独处理,可能反而会减慢渲染速度。这里以Bai度为例:bai度的页面也只有4个分层,这个数量一般是浏览器自动计算,我们不能控制。但是我们可以协助浏览器优化:
will-change
属性:提示浏览器某个元素即将发生变化,帮助浏览器提前做出优化决策,但应谨慎使用,避免无谓的优化。- 减少绝对定位或固定定位:非元素确实需要这些定位属性来实现特定的布局或动画效果,否则应尽量减少使用,因为这类元素往往会被分配到新的图层中。
6.视图绘制
主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来,生成页面图。这些指令集是一种详细的描述,说明了绘制操作的序列,比如填充颜色、描边、绘制文本或图像等,以及这些操作应在图层的什么位置执行。这一过程确保了每个图层的内容能够被准确地描绘出来。
渲染主线程的⼯作到此为⽌,剩余步骤交给其他线程完成。随后,这些绘制指令可能被传递给图形处理单元(GPU)进行硬件加速渲染,特别是在涉及复杂动画、3D变换或大量像素操作时。GPU并行处理的能力可以大大提高绘制的效率,尤其是在高分辨率和高性能要求的场景下。
到这里我们便清楚了浏览器的渲染引擎的工作:为了提高我们理解深度,下面我们继续讨论一下GPU的绘制执行。
GPU工作
渲染引擎最后生成的一系列指令将指导GPU如何进行视图的最终绘制。这些指令包括了图层的组合顺序、颜色信息、纹理、透明度以及任何需要动画效果的属性变化等。
分块 (Tiling)
合成线程首先对每个图层进行分块,将其划分为更多的小区域。它会从线程池中拿取多个线程来完成分块工作。
分块的⼯作是交给多个线程同时进⾏的
光栅化 - Raster
光栅化本身是指将矢量图形、几何形状等转换为像素的过程,在这个阶段,GPU会计算多边形的顶点信息,确定哪些像素属于多边形内部,然后对这些像素进行着色,应用纹理、阴影等效果。由于视口优先渲染原则:将每个块变成位图优先处理靠近视⼝的块。这样可以确保用户首先看到的是最清晰、最互动的部分,而远处或当前不可见的内容可能被降低细节级别渲染,或者推迟渲染。
绘制
合成线程拿到每个层、每个块的位图后,生成一个个指引(quad)信息。指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。
变形发生在合成线程,与渲染主线程无关,这就是transform
效率高的本质原因。 合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像。
常见面试问题
什么是 reflow ?
reflow(回流)过程本质上涉及对文档元素布局的重新计算,即重建布局树。当某些变化使得DOM元素的几何属性(例如宽度、高度以及位置)时,就会触发reflow。这些变化可能包括DOM元素的添加或删除、CSS样式的修改等。进而波及到整个渲染树乃至整个页面布局的重新计算。因此,为了提升性能,浏览器会尽量减少reflow的发生次数。一种策略是将多个可能引起reflow的操作累积在一起,在JavaScript执行完毕后一次性执行布局重新计算,这意味着这类变动导致的reflow往往是异步处理的。
但是也存在一个问题:如果JavaScript代码在更改布局但尚未实际触发reflow之前尝试读取某个元素的布局属性(比如offsetWidth),它可能无法得到更新后的值,因为布局更新还未执行。为了解决这一问题,浏览器在读取某些特定的布局属性时,也会立即触发同步的reflow来更新布局信息,从而确保返回给JavaScript的布局属性是最新的。
这里就是就是原本的这三步操作将会异步一同执行,而因为存在中间访问console.log(test.offsetWidth)
这里的浏览器异步操作就被中断进而发生了两次回流,JavaScript代码本身是同步执行的。
什么是 repaint?
repaint 的本质就是重新根据分层信息计算了绘制指令。当改动了可见样式后,就需要重新计算,会引发 repaint。由于元素的布局信息也属于可见样式,所以 reflow 一定会引起 repaint。重绘直接跳过了布局阶段,不会改变Render Tree的结构,即使颜色信息改变了,这也是在保持Render Tree结构不变的前提下,对节点上存储的样式属性所做的更新。仅影响到绘制阶段,是对页面元素外观的更新。
为什么 transform 效率⾼?
因为 transform 既不会影响布局也不会影响绘制指令,它影响的只是渲染流程的最后一个GPU绘制阶段
由于 GPU绘制阶段 在合成线程中,所以 transform 的变化几乎不会影响渲染主线程。反之,渲染主线程无论如何忙碌,也不会影响 transform 的变化。
结尾
到这里我们的浏览器底层渲染原理基本在以学习面试为方向的读者已经够了,文章内容很干建议可以多阅读理解。
阅读这里的读者,感谢阅读,欢迎评论区讨论。写作不易感谢点赞支持。