系列开篇
为进入前端的你建立清晰、准确、必要的概念和这些概念的之间清晰、准确、必要的关联, 让你不管在什么面试中都能淡定从容。没有目录,而是通过概念关联形成了一张知识网络,往下看你就明白了。当你遇到【关联概念】时,可先从括号中的(强/弱)判断简单这个关联是对你正在理解的概念是强相关(得先理解你才能继续往下)还是弱相关(知识拓展)从而提高你的阅读效率。我也会定期更新相关关联概念。
面试题
- 谈谈重排和重绘的区别
- 如何减少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属性为
absolute
或fixed
的元素,使用绝对定位会使的该元素单独成为渲染树中 body 的一个子元素,重排开销比较小,不会对其它节点造成太多影响。当你在这些节点上放置这个元素时,一些其它在这个区域内的节点可能需要重绘,但是不需要重排。 - 使用虚拟DOM的脚本库。
- 使用css3硬件加速,可以让
transform、opacity、filters
这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color
这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。 - 避免使用CSS表达式,可能会引发回流。
其他
在你快速阅读时,这部分可以不看,甚至有些弱关联的概念也可以简单带过,抓住你本次想了解的核心内容,不纠结把文章看完,而是一次弄清一个概念,反复咀嚼消化,直至你用起来得心应手,知其所以然。
我们可以通过 (浏览器开发者工具的组件) Performance【工具使用技巧】 查看渲染过程,利用工具量化你的性能指标提升,可查看以下指标
- Recalculate style(重新计算元素样式)
- Layout(页面布局) -> Reflow
- Paint(合成的图层被绘制到显示画面的一个区域) -> Repaint
- Composite layers (合成图层)
这里有个 【工具使用技巧】 希望可以开一些工具类使用专栏文章可以介绍下工具的基本使用,记录下常用的技巧。文档查阅是最好的,全量知识,但有时候二八原则会让我们更聚焦于开发目标。