浏览器111

0 阅读13分钟

一、浏览器渲染流程

  1. HTML文档解析

渲染的第一步是解析 HTML 文档

解析过程中中遇到HTML元素会解析HTML元素最终生成DOM树,遇到 CSS 会下载并解析 CSS,遇到 JS会暂停解析HTML,而是去下载并执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。

因为浏览器无法直接理解和使用html,所以需要将html转换为浏览器能够理解的结构——DOM树。 在渲染引擎内部,有一个叫 HTML 解析器(HTMLParser)的模块,它的职责就是负责将 HTML 字节流转换为 DOM 结构。

生成DOM树

解析的过程中遇到HTML元素会解析HTML元素最终生成DOM树

生成CSSOM树

解析的过程中遇到style标签link元素行内样式CSS样式,会解析CSS生成CSSOM树

CSS不会阻塞HTML解析

如果主线程解析到link位置,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因。

CSSOM构建

  • 不会阻塞DOM树的构建(现代浏览器)
  • 但会阻塞渲染 构建(必须等待CSSOM完成)
  • 会阻塞后续JavaScript执行(JS可能依赖样式)

JS会阻塞HTML解析

如果主线程解析到script位置,会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML这是因为 JS 代码的执行过程可能会修改当前的 DOM **** ,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因。

  • JavaScript

    • 同步脚本(<script>)会立即阻塞 DOM 构建
    • 遇到脚本时,必须等待当前所有 CSS 下载完成才执行(避免JS操作未解析的样式)

script标签中defer和async的区别

如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。

下图可以直观的看出三者之间的区别:

其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。

其区别如下:

  • 执行顺序: 多个带async属性的标签,不能保证加载的顺序;多个带defer属性的标签,按照加载顺序执行;

  • async:遇到scirpt标签时,浏览器开始异步下载,下载完成后如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析(可能会阻塞)

  • defer:遇到scirpt标签时,浏览器开始异步下载,html页面解析完才执行js文件。(立即下载,但延迟执行(整个页面都解析完毕之后再执行,不阻塞)

第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。

  1. 样式计算

  1. CSS 结构化:将外部样式表(<link>)、内部样式表(<style>)、内联样式(style属性)转换为浏览器可理解的StyleSheet结构;

  2. 属性标准化:将 CSS 属性值转换为标准化格式(如2em → 32pxblue → rgb(0,0,255)bold → 700);

  3. 样式 继承 与层叠

    1. 继承:子节点继承父节点的可继承属性(如font-sizecolor);
    2. 层叠:按 “选择器权重→声明顺序→来源(用户代理样式 < 用户样式 < 内联样式)” 规则解决样式冲突;
  4. 计算最终样式:为每个 DOM 节点生成ComputedStyle(计算样式),存储所有属性的最终值。

性能关键点

  • 复杂选择器(如div > ul li a)会增加样式计算耗时,建议简化选择器(如使用类选择器.link);
  • CSS 规则匹配是 “从右到左” 的(如.container .item先匹配.item再匹配.container),避免通配符*和深层嵌套。

  1. 布局

3.1 核心步骤

布局的核心是计算 DOM 节点的几何属性(位置、大小、边距),生成布局 (Layout Tree

  1. 构建布局 :基于 DOM 树,过滤掉不可见节点(如<head>display: none的元素),保留可见节点;

  2. 布局计算

    1. 从根节点开始,递归计算每个节点的几何属性(x/y 坐标、宽高、margin/padding/border);
    2. 遵循盒模型规则,结合视口大小、父节点布局约束完成计算;
  3. 生成布局 :布局树仅包含可见节点的几何布局信息,与 DOM 树结构不完全一致(如display: none节点被剔除)。

  • 布局是“自上而下”的:从根节点开始,依次计算每个子节点的几何属性,因为父元素的大小和位置会影响子元素。
  • 布局是“流式布局”:浏览器会按照文档流的顺序,依次计算元素的位置,一旦计算完成,就会确定元素在页面中的最终位置(除非后续触发回流)。

3.2 关键特性
  • 回流(Reflow) :布局计算是递归的,子节点布局变化会触发父节点重新计算,开销极大;
  • 布局抖动(Layout Thrashing) :频繁读取 + 修改布局属性(如offsetTop+style.top)会强制浏览器反复计算布局,导致性能暴跌。

当修改了节点的几何属性,如大小、位置,就需要重新计算布局,这个过程也叫做回流或者重排(reflow)

获取节点的几何属性时,如 offsetWidth / getBoundingClientRect/clientWidth强制重排

  1. 分层

  • 主线程会使用一套复杂的策略对整个布局树中进行分层。
  • 将页面进行分层,之后某个层变化时,就可以单独更新这一个图层,从而避免了全页面的更新,提高效率。
  • 滚动条、堆叠上下文、transformopacity 等样式都会或多或少的影响分层结果,也可以通过will-change属性更大程度的影响分层结果。

  1. 绘制

绘制阶段为每个图层生成绘制指令列表,定义 “先画什么、后画什么”(如先画背景,再画边框,最后画文本)。

核心流程
  1. 渲染引擎将图层的绘制过程拆解为原子化指令(如 “绘制矩形”“绘制文本”“绘制渐变”);
  2. 按绘制顺序组合指令生成绘制列表(Paint List);
  3. 主线程将绘制列表提交给合成线程。
性能关键点
  • 绘制指令越复杂(如多层阴影、渐变),绘制耗时越长;
  • 避免给大尺寸图层添加复杂绘制属性(如box-shadow)。

  1. 分块??

完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。

合成线程首先对每个图层进行分块,将其划分为更多的小区域。

它会从线程池中拿取多个线程来完成分块工作。

  • 大尺寸图层(如全屏滚动区域)会占用大量的GPU内存,消耗资源效率低
  • 合成线程会调用多个线程把每个分层都分成更小的(Tile),通常为256x246或者512x512像素

  1. 光栅化

上面我们已经获得了文档结构、元素的样式、元素的几何关系、绘画顺序,接下来把这些信息转化为显示器中的像素才能显示,这个转化的过程,就叫做光栅化。此过程是合成器的光栅工作线程把每个块变成位图,位图可以理解成内存里的一个二维数组,这个二维数组记录了每个像素点信息

合成线程会将块信息交给 GPU 进程,以极高的速度完成光栅化。GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。光栅化的结果,就是一块一块的位图。

最后一个阶段就是

GPU 硬件加速的原理有两个原因:

  • 使用某些css属性后,渲染引擎会把该把元素单独分层交给 GPU 渲染,GPU处理图形计算更快;

  • 分层渲染不会造成页面的回流重绘;

  1. 总结

再让我们来回顾一遍完整过程:


二、重排、重绘与合成

页面交互过程中,JS/CSS 修改会触发渲染流水线的局部更新,按开销从高到低分为三类:

类型触发条件涉及渲染阶段性能开销优化优先级
重排(Reflow)几何属性变化:宽高、位置、display、DOM 增删布局→分层→绘制→栅格化→合成极高最高
重绘(Repaint)绘制属性变化:颜色、背景、阴影、边框色绘制→栅格化→合成中等
合成(Composite)合成属性变化:transform、opacity仅合成阶段极低低(优先用)

4.1 典型触发场景

4.2.1 重排触发场景
  • 修改几何属性:width: 200pxleft: 10pxmargin: 8px
  • 增删 / 移动 DOM 节点:appendChildremoveChildinsertBefore
  • 窗口操作:resizescroll(部分浏览器优化了 scroll 的重排);
  • 读取布局属性:offsetTopclientWidthgetComputedStyle(强制浏览器提前完成重排)。
4.2.2 重绘触发场景
  • 修改颜色属性:color: redbackground-color: #000
  • 修改边框 / 阴影:border-color: bluebox-shadow: 0 0 10px #000
  • 修改文本样式:text-shadow: 1px 1px 2px #333
  • 修改背景:background-image: url(new.png)background-position: center
4.2.3 合成触发场景
  • transformtranslate/scale/rotate/skew
  • opacityopacity: 0.5
  • will-change:提前声明元素即将变化的属性。

三、渲染性能优化:原理与实战

5.1 核心优化原则

  1. 避免重排,减少重绘,优先合成
  2. 减少渲染工作量(精简 DOM、简化样式);
  3. 利用 GPU 加速(合理创建图层);
  4. 避免主线程阻塞(优化 JS 执行、减少长任务)。

5.2 分阶段优化方案

5.2.1 DOM 构建阶段优化
  • 精简 HTML 结构,减少嵌套层级(如避免超过 6 层嵌套);
  • 延迟加载非首屏 JS:<script defer>/<script async>,避免阻塞 DOM 解析;
  • 预加载关键资源:<link rel="preload" href="critical.css">
5.2.2 样式计算阶段优化
  • 简化选择器:用类选择器(.item)替代标签 / 层级选择器(div > ul li);

  • 避免通配符:* { margin: 0 }会遍历所有节点,增加计算耗时;

  • 抽离通用样式:减少重复样式声明,降低层叠计算复杂度。

底层渲染优化核心逻辑(基于原理的优化方案)

渲染优化的核心,本质是“减少主线程的工作量”和“避免不必要的渲染操作”,结合前面的底层原理,优化方案均对应具体的渲染环节,以下是底层优化的核心逻辑和实操方案。

3.1 优化资源加载(解决阻塞问题)

  • 优化 CSS 加载:将关键 CSS 内联到 HTML 头部(减少关键 CSS 加载时间,避免白屏);非关键 CSS 延迟加载(如通过 media="print" 标记,或动态加载);避免 @import 引入 CSS(@import 会阻塞 CSS 解析,且需等待父 CSS 加载完成)。
  • 优化 JS 加载:使用 defer/async 属性(避免 JS 阻塞 HTML 解析);将 JS 脚本放在 HTML 底部(或动态加载);拆分 JS 代码(首屏仅加载必需的 JS,非首屏 JS 延迟加载);避免同步加载大型 JS 脚本。
  • 优化 图片 加载:使用懒加载(lazyload 属性,或动态加载);使用合适的图片格式(如 WebP、AVIF,减小图片体积);设置图片的宽高属性(避免图片加载完成后,触发布局重排)。

3.2 优化解析过程(减少解析耗时)

  • 简化 HTML 结构:减少 DOM 节点数量(避免嵌套过深,如嵌套不超过 6 层);避免无效 HTML 标签(如多余的 div 嵌套);使用语义化标签(如 header、footer,减少不必要的 class)。
  • 简化 CSS 选择器:避免复杂的后代选择器、通配符选择器(减少 CSS 解析和样式计算耗时);使用类选择器替代标签选择器、后代选择器;避免过度使用 !important(增加样式优先级计算耗时)。
  • 避免 JS 阻塞解析:减少同步 JS 执行时间(避免长时间运算);避免在 JS 中使用 document.write()(修改 HTML 内容,打断解析);合理使用 defer/async。

3.3 优化布局与重绘(减少性能损耗最大的操作)

  • 避免频繁触发回流/重绘
  • 批量修改 DOM 和样式(如使用 DocumentFragment 批量添加节点,或先隐藏节点 display: none,修改完成后再显示)。
  • 避免频繁读取布局属性(如 offsetWidth、clientHeight、getBoundingClientRect()),若需多次读取,可缓存结果。
  • 使用 transform 和 opacity 实现动画(仅触发合成,不触发布局和重绘),替代修改 width、height、top 等几何属性。

优化布局计算:避免使用百分比、em、rem 等依赖父节点几何信息的单位(减少递归计算);固定布局尺寸(如设置固定宽高,避免自适应导致的布局重排);使用 flex/grid 布局(性能优于传统的 float 布局,布局计算更高效)。

优化绘制:减少绘制区域(如使用 contain: paint 属性,限制节点的绘制范围);避免复杂样式(如渐变背景、模糊阴影,可替换为图片);避免频繁修改非几何属性(如 color、background-color)。

3.4 优化合成环节(利用硬件加速)

  • 合理使用硬件加速:对需要动画的节点,使用 transform: translateZ(0) 或 will-change: transform 强制分层,触发硬件加速;但需控制合成层数量(避免 GPU 内存不足)。

  • 避免合成层爆炸:不要为所有节点强制分层;避免使用过多的 opacity < 1、filter、position: fixed 等属性(会自动生成合成层);定期检查合成层数量(通过 Chrome DevTools 的 Layers 面板)。

  • 优化动画性能:动画尽量使用 transform 和 opacity(仅触发合成);避免在动画中修改 DOM 和 CSS(触发布局/重绘);使用 requestAnimationFrame 控制动画(与浏览器渲染帧同步,避免卡顿)。

底层常见问题与排查方法

结合底层渲染原理,日常开发中遇到的渲染卡顿、白屏、样式错乱等问题,都能找到对应的底层原因,以下是常见问题及排查方法(基于 Chrome DevTools)。

4.1 常见问题及底层原因

  • 首屏白屏时间过长:底层原因:关键 CSS 加载缓慢、JS 阻塞解析、HTML 解析耗时过长、资源加载优先级不合理。
  • 界面 卡顿 (滚动/动画卡顿) :底层原因:主线程被 JS 执行阻塞、频繁触发回流/重绘、合成层过多导致 GPU 内存不足、动画未使用硬件加速。
  • 样式错乱:底层原因:CSS 优先级计算错误、样式继承异常、渲染树构建时节点筛选错误、层叠顺序(z-index)设置错误。
  • 回流/重绘频繁:底层原因:JS 频繁修改 DOM/CSS、频繁读取布局属性、图片未设置宽高、窗口大小频繁变化。