浏览器必备知识—回流&重绘

1,237 阅读5分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第4篇文章,点击查看活动详情

页面渲染

  • layout 阶段:这个阶段其实就是我们所说的回流 reflow,浏览器会根据渲染树生成对应的几何信息,比如 DOM 元素相对于浏览器视口的坐标以及大小,最终生成布局树

    • 如果不是页面初始化阶段,回流会使渲染树中受到影响的部分失效,并重新构造渲染树失效的部分,然后再重新计算元素的位置和几何信息生成布局树
  • painting 阶段:这个阶段其实就是我们所说的重绘 repaint,浏览器会根据 layout 阶段生成的布局树将 DOM 元素的几何信息以及大小转换为屏幕中的实际像素

构建渲染树的过程

  1. 遍历 DOM 树的所有可见节点
  1. 对于每个可见的节点,找到 CSSOM 树种对应的样式规则
  1. 结合可见节点和他们对应的样式,合并成渲染树

上面所提到的可见节点是什么呢?真的就是页面中看不到的节点吗?

所谓可见节点就是一些不会渲染到页面中的节点,比如 scriptmetalink等,还有通过设置 display: none 这一个属性隐藏的节点,其它属性比如说 visibilityopacity 只能算是让元素不可见,但对应的节点依旧会出现在渲染树中

总结一下,渲染树中是不会出现样式设置了 display: none 的节点以及 scriptmeta 等不会渲染到页面中的节点的,但是通过 visibilityopacity 进行隐藏的节点依然会出现在渲染树中

什么时候会回流?

上文中也说了,回流的目的就是根据渲染树得到对应 DOM 元素相对于视口的位置和几何信息,那么当这些信息发生改变的时候就会触发回流。简单来说,当元素的布局、大小、内容等发生变化的时候浏览器就需要通过回流重新计算对应 DOM 元素在视口中的几何信息,如下的操作会导致回流:

  • 添加或删除可见的 DOM 元素
  • 元素的位置发生变化

    • 页面中出现滚动条的时候,所有元素的位置都会发生变化从而造成全部元素回流
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
  • 页面一开始渲染的时候(这是必须的,因为 HTML 渲染一定会走 layout 阶段)
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

注意:回流一定会触发重绘,但重绘不一定会回流

浏览器优化

由于回流是比较消耗性能的一个浏览器操作,因为其涉及到了 DOM 节点位置和几何信息的计算,并且回流还会触发浏览器的重绘,所以现代的很多浏览器都会对回流进行优化,比如通过维护一个需要回流的队列,当队列到了一定长度或者到达一定时间才会去清空队列进行回流,这样浏览器就将多次回流重绘转换成了一次回流重绘,大大提高了性能

但是当你获取布局信息的操作的时候,会强制队列刷新进行回流,因为浏览器要确保你获取到的是相关 DOM 元素的最新信息,具体有以下的属性和方法会清空队列,但如果队列为空,则不需要再进行回流重绘,因为在此之间并没有改变位置和几何信息的操作:

  • offsetTopoffsetLeftoffsetWidthoffsetHeight
  • scrollTopscrollLeftscrollWidthscrollHeight
  • clientTopclientLeftclientWidthclientHeight
  • getComputedStyle
  • getBoundingClientRect

减少回流和重绘

  1. 使用 cssText 将多次样式更改合并成一次样式更改,或者直接更新 css 类名 class
  1. 使用脱离文档流 -> 进行复杂操作 -> 恢复文档流的方法减少回流和重绘的次数,比如可以先为元素设置 display: none,操作结束后再把它显示出来,这样总共就只造成了两次回流重绘,原先中间需要回流的地方都省去了
  • 也可以利用这个思想使用绝对定位、文档片段 createDocumentFragment 或克隆元素 cloneNode 的方式实现回流重绘
  1. 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来
  1. 使用 css3 硬件加速,可以让 transformopacityfilterswill-change 这些动画不会引起回流重绘 (但会提高内存占用)
  1. 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
  1. 使用 visibility 替换 display: none,因为前者只会引起重绘,后者会引发回流(改变了布局)
  1. 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局,让回流的压力变大