likes
comments
collection
share

跟面试官说我是这样理解浏览器渲染流程的(中)

作者站长头像
站长
· 阅读数 21

本文首发于托儿所的前端秘籍

计算布局

计算布局是一个很复杂的过程。这个过程并不符合我们上篇总结的三定律:

  • 开始每个子阶段都有其输入的内容;
  • 然后每个子阶段有其处理过程;
  • 最终每个子阶段会生成输出内容。

在此之前我们需要先了解面试中经常被问到的两个概念回流和重绘:

1. 回流

跟面试官说我是这样理解浏览器渲染流程的(中)

当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的 DOM 元素
  • 激活 CSS 伪类(例如::hover)
  • 查询某些属性或调用某些方法

2. 重绘

跟面试官说我是这样理解浏览器渲染流程的(中)

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

好了复习完概念的八股文就来说一下复杂的原因,在执行布局计算的时候是将布局运算的结果重新写入到布局树中,这是一个不太合理的地方,在这个过程中输入和输入的内容并没有分开。所以在开发中优化性能就涉及到了如何避免重绘或者回流,不然一个不合理的操作造成页面过多回流影响页面性能。

如何避免回流

CSS

  • 避免使用 table 布局。
  • 尽可能在 DOM 树的最末端改变 class。
  • 避免设置多层内联样式。
  • 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上。
  • 避免使用 CSS 表达式(例如:calc())。

Javascript

  • 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。
  • 避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。
  • 也可以先为元素设置 display: none,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。

针对这个不合理的问题,Chrome 团队正在重构布局代码,下一代的布局系统为 LayoutNG,试图更清晰的分离输入和输出,从而让新设计的布局算法更加简单。

分层

虽然经过 DOM生成、样式计算和布局已经有了 DOM树,但是还是不能开始绘制页面。在当前的前端页面会有很多复杂的交互效果,例如3D动画、页面滚动、透明度、滤镜和层级(z-index)等会。为了更好的将这些效果呈现在屏幕上,渲染引擎需要将特定的节点生成专用的图层,并生成一颗对应的图层树(LayerTree)。如果熟悉PS等设计软件就很容易理解图层的概念,然后最终将这些图层叠加在一起生成最后的图像。

可以通过 Chrome 控制台中的 Layers (macos 在more tools 菜单中) 标签查看可视化分层的情况,如下图:

跟面试官说我是这样理解浏览器渲染流程的(中)

Layers菜单

跟面试官说我是这样理解浏览器渲染流程的(中)

掘进首页分层图

从图中就可以看出渲染引擎是将页面分成了好多层级最终页面是多个层级叠加在一起形成的:

跟面试官说我是这样理解浏览器渲染流程的(中)

页面最终效果

下面看一下图层树和节点树的关系:

跟面试官说我是这样理解浏览器渲染流程的(中)

图层树和节点树的关系

通常情况下,并不是每个节点树的每个节点都包含一个图层,如果一个节点没有对应的层那就归属于父节点的图层,如图中的 span 标签没有图层,那么这个节点就是从属于父图层节点。

什么条件下才会触发渲染引擎为特定的节点创建新的图层呢?

1.拥有层叠上线文属性的元素会被体层为单独的一层

页面是个二维平面,但是层叠上线文能够让 HTML 元素拥有三维概念,这些 HTML 元素按照自身属性优先级分布在垂直于这个二维平面的 Z 轴上,如下图所示:

跟面试官说我是这样理解浏览器渲染流程的(中)

层叠上线文

如果想要了解更多的层叠上下文内容可参考 MDN

2.需要裁剪(clip)的地方会被创建为图层

首先来了解一下什么是裁剪:

<style>
  div {
    width: 200;
    height: 200;
    overflow:auto;
    background: gray;
    } 
</style>
<body>
<div >
    <p>所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:</p>
    <p>从上图我们可以看到,document层上有A和B层,而B层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p>
    <p>图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p> 
</div>
</body>

在这里我们把div的大小限定为200 * 200像素,而div里面的文字内容比较多,文字所显示的区域肯定会超出200 * 200的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在div区域,下图是运行时的执行结果:

跟面试官说我是这样理解浏览器渲染流程的(中)

裁剪结果

出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。你可以参考下图:

跟面试官说我是这样理解浏览器渲染流程的(中)

裁剪裁剪内容单独一层

所以说,元素有了层叠上下文的属性或者需要被剪裁,满足其中任意一点,就会被提升成为单独一层。

小结

跟面试官说我是这样理解浏览器渲染流程的(中)

渲染流水线

本篇主要介绍了渲染流程的分层,同时也补上了上篇遗留的渲染计算的坑。

下篇主要介绍图层绘制、栅格化、合成和显示三个部分,敬请期待。