页面展现中的浏览器渲染原理

1,025 阅读6分钟

欢迎关注我的个人博客分享一些前端技术、面试题、面试技巧等

引入

提到页面渲染,最重要的就是关键渲染路径

通常我们在编写页面时,只需要编写 HTML、Css、JavaScript 屏幕上就会显示出漂亮的页面,但浏览器是如何使用我们的代码在屏幕上渲染像素的呢? 浏览器将 HTML、Css、JavaScript 转换为屏幕上所呈现的实际像素,这期间所经历的一系列步骤,叫做关键渲染路径(Critical Rendering Path)

浏览器渲染页面的过程

从耗时的角度,浏览器请求,加载,渲染一个页面,时间花在下面五项

  • DNS 查询
  • TCP 连接
  • HTTP 请求及响应
  • 服务器响应
  • 客户端渲染

本文讨论第五个部分,即浏览器对内容的渲染

  1. 处理 HTML 生成 DOM 树
  2. 处理 Css 生成 CssDOM 树
  3. 将两树合并成 render 树
  4. 对 render 树进行布局计算
  5. 将 render 树中的每个节点绘制到屏幕上

如果 DOM 或 CssDOM 被修改,以上过程需要重复执行,这样才能计算出那些像素在屏幕上进行重新渲染。

细化分析

  1. 浏览器把获取到的 HTML 代码解析成一个 DOM 树,HTML 中的每个 tag 都是 DOM 树中的 一个节点,根节点就是我们常用的 document 对象,当然这里包含使用 js 动态创建的 DOM 节点
  2. 浏览器把所有样式(主要包括 Css 和浏览器的默认样式设置)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,生成 CssDOM 树
  3. DOM tree 和 CssDOM tree 合并成 render tree, render tree 中每个 node 都有自己的 style,而且 render tree 不包含隐藏的节点(比如display: none的节点,还有无样式的<head>节点),因为这些节点不会用于呈现,而且不会影响呈现的。注意:visibility: hidden隐藏元素还是会包含到 render tree 中,因为visibility: hidden会影响布局(layout),会占有空间
  4. render tree 构建完毕之后根据样式计算布局,布局阶段的输出结果都称为"盒模型"(box-model)。盒模型精准表达了窗口每个元素的位置和大小,而且所有的相对度量单位都被转换成了屏幕上的绝对像素位置(根据 Css2 的标准,render tree 中的每个节点都称为 box),box 所有属性:width,height,margin,padding,left,border...
  5. 将这些信息渲染为屏幕上每个真实的像素点,这个阶段称为"绘制",或者"栅格化"。

mymf3T.png

渲染阻塞

现代的浏览器总是并行加载资源。

当 HTML 解析器被脚本阻塞时,解析器虽然会停止构建 DOM,但仍会识别该脚本后面的资源,并进行预加载。

  1. 默认情况下,Css 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直到 CssDOM 构建完毕
  2. JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CssDOM 属性

存在阻塞的 Css 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建

  1. 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行
  2. JavaScript 可以查询和修改 DOM 与 CssDOM
  3. CssDOM 构建时,JavaScript 执行将暂停,直至 CssDOM 就绪

script 标签的位置很重要,实际使用时应将资源放在页面底部

defer 和 async

注意:async 与 defer 属性对于 inline-script 都是无效的

defer

defer 属性表示延迟执行引入的 JavaScript,在这段代码加载时 HTML 并未停止解析,这两个过程是并行的。当整个 DOM 解析完毕且 defer-script 也加载完成后,执行 defer-script 里的 JavaScript 代码。

执行结果放在 HTML 标签解析完成之后

async

async 属于异步加载 JavaScript,在这段代码加载时 HTML 并未停止解析,这两个过程是并行的。当 async-script 加载完成后,会立即执行 async-script 里的 JavaScript 代码。

执行结果可能在 HTML 标签解析完成之前或之后

动态创建标签

当使用document.createElement('script')创建的 script 默认是异步的

console.log(document.createElement("script").async); //true

重绘重排

重排重绘会影响性能

  1. 我们计算它们在当前设备中的准确位置和尺寸。这正是布局阶段要做的工作,该阶段在英语中也被称为 "回流"(reflow),当 render tree 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等给便需要重新构建。也会回流(其实我觉得叫重新布局简单明了些)。每个页面至少需要一次回流,就是在页面第一次加载的时候。
  2. **重绘(repaints)**当 render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局,比如background-color,则就称为重绘

触发重排的方法

以下这些属性和方法需要返回最新的布局信息,重新计算渲染树,就会造成回流,触发重排以返回正确的值。建议将他们合并到一起操作,可以减少回流的次数。这些属性包括:offsetTopoffsetLeftoffsetWidthoffsetHeightscrollTopscrollLeftscrollWidthscrollHeightclientTopclientLeftclientWidthclientHeightgetComputedStyle()currentStyle()

提高网页性能,就是要降低"重排"和"重绘"的频率和成本,尽量少触发重新渲染。

DOM 变动和样式变动,都会触发重新渲染。但是,浏览器已经很智能了,会尽量把所有的变动集中在一起,排成一个队列,然后一次性执行,尽量避免多次重新渲染.

优化办法

理论上的优化办法

  1. DOM 的多个读操作,(或多个写操作),应该放在一起,不要两个读操作之间插入一个写操作
  2. 离线操作 DOM。如使用隐藏元素 document.createDocumentFragment(),cloneNode()
  3. 修改样式的时候添加类名,或一次性添加到 dom.style.cssText上等

希望对读完本文的你有帮助、有启发,如果有不足之处,欢迎批评指正交流!

欢迎关注我的个人博客分享一些前端技术、面试题、面试技巧等

辛苦整理良久,还望手动点赞鼓励~


'摘抄'不是单纯的“粘贴->复制”,而是眼到,手到,心到的一字一句敲打下来。

博客声明:所有转载的文章、图片仅用于作者本人收藏学习目的,被要求或认为适当时,将标注署名与来源。若不愿某一作品被转用,请及时通知本站,本站将予以及时删除。