前端性能优化面面谈[3]-页面重绘repaint和回流reflow

你是不是经常听师兄或一些前端前辈说不能用CSS通配符 *,CSS选择器层叠不能超过三层,CSS尽量使用类选择器,书写HTML少使用table,结构要尽量简单-DOM树要小….等这些忠告,以前我就大概知道使用通配符或者CSS选择器层次过多可能会降低性能,至于为什么不使用table标签我一直是迷迷糊糊,也就跟着那样做了,但我认识了Repain和 Reflow之后,原来这些还真不能用太多。 ok,希望这篇文章对你有帮助!

上面是引用的别人的一段话,大概意思是说理解reflow和repaint还是有很大价值的,他能够让我们用最简单的方法记住那些页面开发中的优化点。而这些优化点,都是面试题…

介绍

那么reflow和repaint是什么呢? 我们知道一个页面从代码到页面,是浏览器读取我们的html,css代码,然后渲染出来的。
而这个渲染的过程,其实往细里想也知道,浏览器必须根据你的css和html准备好一个完整的样式和dom的数据结构(一个大家称之为渲染树的东西),然后浏览器要去计算出这个页面上所有dom的布局,即每个dom元素的盒子大小、位置(这个过程就是计算布局,人们所以称之为reflow回流)。每个元素的坐标、大小计算好了之后,浏览器就根据每个元素上的css属性情况把这个元素画出来(这个过程被称之为repaint重绘)。

然而我们看到中文的翻译,前面带了个,当然英文的reflow,repaint前面也带了个 re. 所以我认为其实平常大家讲reflow,repaint的时候,应该是特指在页面渲染完毕之后,由其他原因造成的页面重新计算布局和重新绘制的信息行为。

而且也很显然,页面第一次渲染这块的计算开销是必然要走的。能够扯到性能优化并且可以有优化空间的,必然是页面运行过程中的reflow和repaint。

所以对浏览器来说,repaint和reflow的目的是:展示一个新的页面样貌。 而浏览器的这个展示过程的合理合法的,而我们之所以要进行性能优化,是因为我们自己发现了在命令浏览器进行repaint和reflow的时候有一定技巧可以减少浏览器reflow/repaint的次数,但实现同样的效果。

区别

我认为他俩的区别如下:

回流reflow:盒子几何的变化,例如大小、坐标位置
重绘repaint:盒子自身的非位置及大小布局的变化。如改变颜色等,不会影响几何。

看下浏览器解析渲染文档的细节:

  1. 解析HTML以构建DOM树:渲染引擎开始解析HTML文档,转换树中的html标签或js生成的标签到DOM节点,它被称为 – 内容树。
  2. 构建渲染树:解析CSS(包括外部CSS文件和样式元素以及js生成的样式),根据CSS选择器计算出节点的样式,创建另一个树 —- 渲染树。
  3. 布局渲染树: 从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标。(你可以把这一步想象为浏览器在页面上画了很多无形的框)
  4. 绘制渲染树: 遍历渲染树,每个节点将使用UI后端层来绘制。

好,我们可以看到Repain 和 Reflow 分别出现在了第三和第四步。因此我们给出下面的定义:

对于DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式(浏览器的、开发人员定义的等)来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow;当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。

可见这两个东东对浏览器渲染页面是很重要的啊,都是会影响性能的,因此我们需要了解一些常见的会引起repaint和reflow的一些操作,并且应该尽量减少以提高渲染速度

引起reflow的操作

  • 缩放浏览器,以及resize浏览器
  • 修改dom元素的位置
  • 增加、删除dom节点
  • width/height/border/margin/padding的修改,如width=778px;
  • 动画,:hover等伪类引起的元素几何改动,
  • display=none等造成dom位置变化。因为display会影响元素是否占空间。
  • scroll页面,这个不可避免。这个带来的reflow是绝对无法避免的,用户肯定要动啊。
  • font类style的修改
  • background的修改,注意着字面上可能以为是重绘,但是浏览器确实回流了,经过浏览器厂家的优化,部分background的修改只触发repaint,当然IE不用考虑;
  • 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)); (这一条,哥也无法理解为啥会触发reflow,仅仅是参考自网络)

引起repaint的操作

如:

  • 修改了元素visibility,元素几何位置还要占用,只是不可见而已。盒子的几何性质没变。
  • 修改元素的outline
  • 修改元素的背景色 background color
  • 修改网页默认字体(这个应该也可能触发reflow的)
  • a:hover这也会的

如何解决?

答: 这是不可避免的。页面中总免不了要操作DOM的。
只能说我们要将reflow对性能的影响减到最小

最佳实践

  • 批量修改样式。比如你js通过style改变top,改变left。这不断造成了2次回流,此时应该直接设置class解决。
    然而,现代浏览器已经可以把多次style样式修改合并为一次reflow执行了。
  • 批量修改dom。不要多次进行DOM增删改等操作,可以使用 document.createDocumentFragment() 把要改的 DOM 节点缓存起来 在内部修改,再一次性添加进 HTML 中。
  • 尽可能在DOM末梢(DOM的叶子节点)通过改变class来修改元素的style属性:尽可能的减少受影响的DOM元素。
  • 设置动画元素position属性为fixed或者absolute:由于当前元素从DOM流中独立出来,因此改变这个元素,没必要reflow布局的,因为受影响的只有当前元素
  • 牺牲平滑度满足性能:动画精度太强,会造成更多次的repaint/reflow,牺牲精度,能满足性能的损耗,获取性能和平滑度的平衡。 也就是说,可以减少动画的每秒帧数。
  • 避免使用table进行布局:table的每个元素的大小以及内容的改动,都会导致整个table进行重新计算,造成大幅度的repaint或者reflow。改用div则可以进行针对性的repaint和避免不必要的reflow。(然而数据展示型的,也没有比表格更合适的了)
  • 避免在CSS中使用运算式:学习CSS的时候就知道,这个应该避免,不应该加深到这一层再去了解,因为这个的后果确实非常严重,一旦存在动画性的repaint/reflow,那么每一帧动画都会进行计算,性能消耗不容小觑。(然而我们一般都不会在css中用这个的…)

谁的代价更高?

reflow是要比repaint快的。但是由于reflow必然会造成repaint,所以reflow的代价更高。
DOM Tree 里的每个结点都会有 reflow 方法,一个结点的 reflow 很有可能导致子结点,甚至父点以及同级结点的 reflow。在一些高性能的电脑上也许还没什么,但是如果 reflow 发生在手机上,那么这个过程是非常痛苦和耗电的

指导意见: 减少reflow…

Refer