浏览器渲染流程

aminzi

网页被解析的过程

从服务器下载 index.html 文件,遇到 link 就下载 css ,遇到 script 就下载 js

ppIiI39.png

ppIFvZV.png

加载 html 然后对 html 进行从上到下解析。

解析 html 的时候 加载 css,加载css完成然后对 css 进行解析。

  • 解析hmtl 的时候,加载css 是不会影响 html 的解析,因为是独立的线程完成的

【第一步】html 解析得到 DOM Tree

【第二步】css 解析得到 Style Rules 可以称为 cssom tree
ppIiWAU.png
【第三步】将 DOM Treecssom Tree 结合在一起 形成 RenderTree

RenderTree 交给 Layout(布局引擎)

Layout 计算布局(大小、位置)信息

注意

RenderTree 是有节点,但是没有节点的大小和位置的信息,只记录节点的样式

为什么没有节点的大小和位置的信息?

  • 因为有些节点是 display:none 是没有出现在 RenderTree

linke 元素不会阻塞 DOMTree 构建过程,但是会阻塞 Render tree 构建过程

RenderTree 需要 cssom treeDOMTree

【第四步】布局(layout)和绘制(paint)

  • 布局

渲染树记录节点样式

布局是呈现节点中的样式的宽度、高度和位置的信息

  • 绘制

在绘制阶段,浏览器将布局阶段计算的每个 frame(样式的宽高和位置) 转为屏幕上实际的像素点

包括元素可见部分进行绘制,比如:文本、颜色、边框、阴影、替换元素(img)

回流(reflow)和重绘(repaint)
ppIihh4.png

ppIFvZV.png

  • 回流

第一次计算节点宽高和位置的时候,是布局

之后再次重新计算节点宽高和位置的时候,是回流

  • 发生回流情况

  • DOM 结构发生变化。

    • 🌰 删除 dom 节点 ,下面的节点会向上顶
  • 改变布局

    • 🌰 修改 width、height、padding、font-size
  • 改变浏览器的窗口大小 (resize)

  • 调用 getComputedStyle 方法 获取尺寸、位置

  • 重绘 repaint

第一次渲染内容,是绘制

之后重新渲染内容,是重绘

  • 发生重绘的情况

修改背景颜色、文字颜色、边框颜色、样式

回流一定引起重绘,所以回流很消耗性能

  • 开发尽量避免发生回流
  • 尽量一次性修改样式
    • 🌰 通 cssText 修改,通过添加 class 修改
  • 避免频繁操作 DOM
    • 如果频繁操作 DOM,可以使用 DocumentFragment 或者父元素中将要操作 DOM 快完成时候,再一次性操作完成
  • 避免 调用 getComputedStyle 方法 获取尺寸、位置
  • 使用 position 和 absolute 或 fixed
    • 虽然会引起回流,但是能减少开销(如果修改节点的大小,那么只计算大小,而减少位置的计算)

特殊解析 - Composite(合成)

浏览器的一种优化手段

绘制的过程,可以将布局后的元素绘制到多个合成图层中

默认情况下,标准流中的内容都是被绘制在同一个图层(Layer)中

  • 每个合成图层都是单独渲染的

一些特殊的属性,会创建一个新的合成层(CompositingLayer),并且新的图层能例用 GPU 加速绘制

一些特殊的属性

  • 3D transforms (translateZ(xxxxpx))
  • video、canvas、iframe
  • opacity 动画转换时;
  • position: fixed (固定定位)
  • will-change:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化;
  • animation 或 transition 设置了 opacity(透明度)、transform

分层确实可以提高性能,但是它以内存管理为代价,因此不应作为 web 性能优化策略的一部分过度使用。

大白话:

那些属性可以在新的图层,使用 gpu 加速绘制,完成后合并到标准流的图层中。

🌰 translateZ(xxxxpx)向 z 轴位移

🌰 opacity(透明度)
ppIi59J.png
注意滥用的代价是创建多个新的图层内存会大,内存换取性能。

will-change 提前告诉浏览器元素可能发生哪些变 对于在标准流图层中不需要使用,这回造成创建多个新的图层会造成内存大。

对于用户交互的方式使用会比较好。因为这些如果在同一个图层中会消耗性能,会引起回流或者重绘,所以避免回流和重绘,需要在新的图层中进行 gpu 加速绘制。比如鼠标互动的一些位移效果

defer / async

默认情况下

浏览器从上到下执行代码,遇到 link 标签加载 css 不会阻塞 DOMTree 的构建。

但是碰到 script 会暂停 DOMTree 构建 也就是阻塞,后面的 dom 不解析了。

需要等待 script 加载和执行完毕,才继续解析后面的 html,构建 DOMTree。

为什么需要等待 script 加载和执行完毕,才继续解析后面的 html,构建 DOMTree?

如果把所有的 html 都解析完,构建 DOMTree 。后面执行 script 可能会有操作 dom。

这个会引起重流和重绘,DOMTree → RenderTree → Layout → 绘制

defer

在解析 html 中,构建 DOMTree 中碰到 script 不会阻塞 DOMTree 的构建。

作用是先下载 script ,等待 DOMTree 的构建完成后,在 DOMContentLoaded 事件之前先执行 defer 中 script 的代码

  • 外多个带 defer 的脚本是可以保持正确的顺序执行的
  • 推荐放到 head 元素中,先最快预先下载完成
  • defer 仅适用于外部脚本,对于 script 默认内容会被忽略

async

没有顺序,完全独立的,无论 DOMTree 构建完成没,下载完成就执行