[核心概念] 一文说透重排和重绘(Reflow/ Repaint)

1,504 阅读7分钟

系列开篇

为进入前端的你建立清晰、准确、必要概念和这些概念的之间清晰、准确、必要关联, 让你不管在什么面试中都能淡定从容。没有目录,而是通过概念关联形成了一张知识网络,往下看你就明白了。当你遇到【关联概念】时,可先从括号中的(强/弱)判断简单这个关联是对你正在理解的概念是强相关(得先理解你才能继续往下)还是弱相关(知识拓展)从而提高你的阅读效率。我也会定期更新相关关联概念。

面试题

  • 谈谈重排和重绘的区别
  • 如何减少reflow/repaint来提高性能

这是干什么的?

首先,Reflow/Layout[重排]和Repaint[重绘]不是一回事。他们是浏览器渲染过程的两个环节

那么你就得先简单了解下浏览器渲染机制了【关联概念】(这个比较复杂,之后再更,先简单说下)

简单来说你能看到的浏览器上面的画面不可能是一成不变的吧,那还看个啥,那么浏览器画面不停变化其实就在不停重新渲染。(所以这两词都有 're' 前缀,表示重新..)

我们知道 HTML CSS 代码最后都会变成图层呈现给我们

那么 Reflow 就是影响了这个图层的尺寸排版

Repaint 则影响了这个图层的颜色等元素尺寸排版无关的样式。

类比

把浏览器想象成个画布,重新渲染就像在不停地重新作画。

重排Reflow就好比这幅画在结构,尺寸,排版等发生变化,你看上去无法在不"擦掉",或换张纸重新画等方式变化那么你就是 Reflow。

如果这幅画只是在背景色,文字色,边框颜色等 css 属性发生变化,其实画的"模板"结构没变化,那么就轻松多了,你只需用颜料盖上去就好了,这就是 Repaint。那么颜料就层层叠叠的了吧(css 叫啥 层叠样式表 Cascading Style Sheets ^-^)

所以直观上说 Reflow的成本比Repaint的成本高得多的多。重绘不一定导致重排(颜色覆盖就行),但重排一定会导致重绘(得重新画布局再上色)。

优化方式

重排和重绘会不断触发,这是不可避免的,否则你将永远看到静态网页。但是,它们非常耗费资源,是导致网页性能低下的根本原因。而且一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。所以我们才需要知道,什么时候会触发重排,什么时候会触发重绘,如何优化。

当然,我们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行reflow。

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

会触发重排的常见情况

  • Initial。网页初始化的时候。
  • 当你增加、删除 DOM结点时。
  • 当你移动DOM的位置,或是搞个动画的时候。
  • 当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
  • 当你修改网页的默认字体时。
  • 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等
  • ......
  • 只要这些行为引起了页面上某些元素的占位面积定位方式边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

会触发重绘的常见情况

  • 改变背景属性(background/background-image/ackground-repeat..),
  • 改变字体颜色(color),注意不是大小
  • visibility 控制显示
  • 加阴影效果 box-shadow
  • ......
  • 只是改变某个元素的背景色、文字颜色等等不影响它周围或内部布局的属性时的操作

优化方案技巧枚举

  • 不要使用table布局。因为可能很小的一个小改动会造成整个table的重新布局。
  • 尽量不要把读操作和写操作,放在一个语句里面。
// bad
div.style.left = div.offsetLeft + 10 + "px";
div.style.top = div.offsetTop + 10 + "px";

// good
var left = div.offsetLeft;
var top  = div.offsetTop;
div.style.left = left + 10 + "px";
div.style.top = top + 10 + "px";
  • 不要一条一条地修改DOM的样式。与其这样,还不如预先定义好css的class,然后修改DOM的className。
// bad
var left = 10,
top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// Good
el.className += " theclassname";

// Good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
  • 把DOM离线后修改。如:
    • 使用 documentFragment 对象在内存里操作DOM。完成后再把这个对象加入DOM。
    • 先把DOM给display:none(有一次reflow),然后你想怎么改就怎么改。比如修改100次,然后再把他显示出来。
    • cloneNode() 方法clone一个DOM结点到内存里,然后想怎么改就怎么改,改完后,和在线的那个的交换一下。
  • 不要把DOM结点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。
  • 尽可能的修改层级比较低的DOM。当然,改变层级比较底的DOM有可能会造成大面积的reflow,但是也可能影响范围很小。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流 position属性为absolutefixed的元素,使用绝对定位会使的该元素单独成为渲染树中 body 的一个子元素,重排开销比较小,不会对其它节点造成太多影响。当你在这些节点上放置这个元素时,一些其它在这个区域内的节点可能需要重绘,但是不需要重排。
  • 使用虚拟DOM的脚本库。
  • 使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。
  • 避免使用CSS表达式,可能会引发回流。

其他

在你快速阅读时,这部分可以不看,甚至有些弱关联的概念也可以简单带过,抓住你本次想了解的核心内容,不纠结把文章看完,而是一次弄清一个概念,反复咀嚼消化,直至你用起来得心应手,知其所以然。

我们可以通过 (浏览器开发者工具的组件) Performance【工具使用技巧】 查看渲染过程,利用工具量化你的性能指标提升,可查看以下指标

  • Recalculate style(重新计算元素样式)
  • Layout(页面布局) -> Reflow
  • Paint(合成的图层被绘制到显示画面的一个区域) -> Repaint
  • Composite layers (合成图层)

这里有个 【工具使用技巧】 希望可以开一些工具类使用专栏文章可以介绍下工具的基本使用,记录下常用的技巧。文档查阅是最好的,全量知识,但有时候二八原则会让我们更聚焦于开发目标。

参考