浏览器渲染原理,过程
记录于 07月15日 · 木曜日9 min read

浏览器渲染原理,过程

我认为作为前端工程师了解 HTML,CSS,JS,浏览器的渲染以及运行原理。

就好比士兵打仗要用枪,那么士兵一定对自身的武器非常了解这样在武器发生故障的时候才可以精准的定位问题解决问题。

那么浏览器,三剑客就是前端工程师的武器 🏹

所以本篇将会作为重学前端笔记的第一章!

我的疑问 🤔:

  • 浏览器是如何将获取的网页数据转换成视图的?

  • 浏览器如何解析三剑客(html,js,css)并渲染,其先后顺序是?

  • 三剑客的进化趋势是?

浏览器渲染原理:

这之前可以先看一下 B 站 UP 主卢克儿的视频 【干货】浏览器是如何运作的?

浏览器结构分为:🌕 用户界面——>浏览器引擎 🌗——>渲染引擎 🌑

浏览器引擎起到将 用户操作渲染引擎 关联起来的作用。

而其中最重要的为渲染引擎,其关乎到网络请求,js 解析等。( Blink,webkit,Gecko

目前的渲染引擎又分为多个进程同时进行工作,网络请求到数据后会通知 ui 进程进行渲染, 而其中应该注意的为渲染器进程中的 GUI Thread( 图形渲染 和 JS Thread ( js 渲染

GUI Thread 将解析 HTML 标签 并创建document对象,并以其为根节点创建 DOM 树,但解析到 script 标签时将会暂停。

(因为标签多为嵌套关系,其父子关系会组成树状结构不难理解吧

原因在于,JavaScript 可以操作 DOM,如果 GUI 正在渲染界面,而 JS 引擎对页面元素属性进行修改, 就会出现界面丢失了 JS 引擎修改部分的情况( 出现不可预期的结果,比如找不到页面元素 )。🤒

故浏览器设置 GUI Thread 和 JS Thread 不能同时工作, 当 JS Thread 执行时,GUI Thread 被挂起(GUI 更新保存在一个队列中,等 JS Thread 空闲时被立即执行)。

所以 script 标签应该放在其合适的位置上

那么浏览器到底是如何将获取的网页数据转换成视图的呢?该部分理解对 JS 和 css 有一定基础要求。

  • 在 DOM 树生成的同时如遇到了 css 代码将会对其进行下载解析, 并生成样式规则树CSSOM 树并附着在 DOM 树上,最终生成一个可渲染树 render tree

😲 注意这个过程:

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

  • 且由于以下两点。浏览器会延迟 JavaScript 的执行和 DOM 构建:

    • CSS 被默认被视为阻塞渲染的资源,因此浏览器将在 CSSOM 构建完毕前不会渲染任何已处理的内容。
    • JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性,因此 CSS 解析与 script 的执行互斥。
  • 正是由于以上这些原因,script 标签的位置很重要我们在实际开发中应该尽量坚持以下两个原则:

    • 在引入顺序上,CSS 资源先于 JavaScript 资源。
    • JavaScript 应尽量少的去影响 DOM 的构建。

  • 可渲染树 render tree生成后将会进行布局解析,对已经计算好样式的 DOM 结点进行遍历生成Layout tree
    • 但 Layout tree 并不是与 DOM 树一一对应的,例如某结点的样式 display:none 那么其不会出现在 Layout tree 上,这就是 display 为 none 时不会占据页面内容, 而 visibility:hidden会的原因。
    • 而伪类(before after 将会出现在 Layout tree 中

所以 Layout tree 将会与页面渲染的最终效果一一对应

  • 那么 Layout tree 生成之后是否可以直接显示了呢?😥 当然是不可以的因为元素之间的层级关系还没有确定( z-index ,主线程通过遍历Layout tree来生成一张层级关系表Paint Record其中记录各元素之间的层级关系。

  • 好!经过页面分层阶段终于可以进行绘制成像素展示在页面上了。绘制的过程称为栅格化

    • 合成器线程将页面所有元素按照一定规则,进行分图层绘制;
    • 然后将浏览器可视部分的内容合成一帧即可;
    • 合成器线程会将页面一帧分成若干个小块,发送给栅格化线程,栅格化各个小块后将其合成。
    • 最终浏览器将合成器帧发送至 GPU 进行渲染最终显示给用户.

可喜可贺,页面终于显示在浏览器上了 🤯,我要是浏览器可能已经累死了。

注意:

以上步骤并不一定一次性顺序完成,比如 DOM 或 CSSOM 被修改时,亦或是哪个过程会重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。而在实际情况中,JavaScript 和 CSS 的某些操作往往会多次修改 DOM 或者 CSSOM。

那么如果页面通过交互改变了样式以及布局呢?

  • 重排/回流:如果页面在绘制完成后发生布局改变,那么渲染器进程将会对页面元素重新进行样式计算及后续过程,这个过程称为重排/回流 reflow
  • 重绘:如果元素发生样式改变,这个过程称为重绘 repaint

所以如果页面动画与 JS 处理同时进行,而 JS 的处理量过大,将会导致,主线程被 JS 占用而无法及时被归还,导致页面丢失动画帧,造成页面卡顿。

那么如何优化页面呢?

结合上文和我看到的一些文章,有以下几点可以优化渲染效率:

  1. 合法地去书写 HTML 和 CSS ,且不要忘了文档编码类型。
  2. 样式文件应当在 head 标签中,而脚本文件在 body 结束前,这样可以防止阻塞的方式。
  3. 简化并优化 CSS 选择器,尽量将嵌套层减少到最小。
  4. 尽量减少在 JavaScript 中进行 DOM 操作。
  5. 修改元素样式时,更改其 class 属性是性能最高的方法。
  6. 尽量用 transform 来做形变和位移

参考文章:

https://zhuanlan.zhihu.com/p/78230297

https://www.bilibili.com/video/BV1x54y1B7RE