在了解浏览器的重排与重绘之前,我们来先了解一下浏览器的渲染机制。
浏览器的渲染机制
对于渲染,我们首先需要了解一个概念:设备刷新率。
设备刷新率是设备屏幕渲染的频率,通俗一点就是,把屏幕当作墙,设备刷新率就是多久重新粉刷一次墙面。基本我们平常接触的设备,如手机、电脑,它们的默认刷新频率都是60FPS,也就是屏幕在1s内演染60次,约16.7ms渲染一次屏幕。
这就意味着,我们的浏览器最佳的渲染性能就是所有的操作在一帧16.7ms内完成,能否做到一帧内完成直接决定着渲染性,影响用户交互。
浏览器渲染的具体流程:
- DOM树与CSSOM树合并后形成渲染树
- 渲染树只包含渲染网页所需的节点
- 布局计算每个对象的精确位置和大小。
- 最后一步是绘制,使用最终渲染树将像素渲染到屏幕上。
总结浏览器的渲染流程:
- DOM树构建
- CSSOM树构建
- RenderObject树构建(呈现树)
- 布局(为每个节点分配一个应出现在屏幕上的确切坐标)
- 绘制
了解了浏览器的渲染机制后,我们来看看什么是重排与重绘。
重排reflow(回流)
reflow指的是重新计算页面的布局。
某个节点reflow时会重新计算节点的尺寸和位置,而且还有可能触发其子节点、祖先节点和页面上的其他节点reflow。在这之后再触发一次repaint。
当render tree 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为回流。每个页面至少需要一次回流,就是在页面第一次加载的时候。
导致reflow的操作:
- 调整窗口大小
- 改变字体
- 增加或者移除样式表
- 内容变化,比如:hover(IE中为兄弟结点伪类的激活)
- 操作class属性
- 脚本操作DOM
- 计算offsetWidth和offsetHeight属性(相对于父元素的边框的偏移量)
- 设置style属性的值
重绘repaint
repaint或者redraw遍历所有的节点,检测各节点的可见性、颜色、轮廓等可见的样式属性,然后根据检测的结果更新页面的响应部分。
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的(颜色,轮廓等),比如background-color。则就叫做重绘。
只触发重绘不触发重排的一些css属性:
- color
- border-style、border-radius
- visibility(改变可见性)
- text-decoration
- background、background-image、background-position、background-repeat、background-size
- outline、outline-color、outline-style、outline-width(输入框的边框颜色等)
- box-shadow(阴影)
例如:
var bstyle=document.body.style;
bstyle.padding="20px"; //reflow,repaint
bstyle.backgroundColor="red"; //repaint
bstyle.fontSize="10px"; //reflow,repaint
需要知道的是,浏览器不会像上面一样,每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫做异步reflow或增量异步reflow。但是,当遇到resize窗口操作,或改变了页面默认的字体时,对于这些操作,浏览器会马上reflow。
减少重排与重绘
重排和重绘在实际开发中是很难避免的,我们能做的就是尽量减少这种行为的发生。
具体方法:
-
js尽量少访问dom节点和css属性,尽量不要过多的频繁的去增加,修改,删除元素,因为这可能会频繁的导致页面reflow,可以先把该dom节点抽离到内存中进行复杂的操作然后再一次性display到页面上(进行一次重排和重绘)。(可以使用React,Vue的虚拟DOM)
-
减少不必要的DOM层级(DOM depth)。改变DOM树中的一级会导致所有层级的改变,上至根部,下至被改变节点的子节点。这导致大量时间耗费在执行reflow上面。
-
不要通过父级来改变子元素样式,最好直接改变子元素样式,改变子元素样式尽可能不要影响父元素和兄弟元素的大小和尺寸
-
尽最通过class来设计元素样式,切忌用style多次操作单个属性
-
尽可能的为产生动画的HTML元素使用fixed或absolute的position,那么修改他们的CSS是不会 reflow的。
-
**img标签要设置高宽,**以减少重绘重排
-
把DOM离线后修改,如将一个dom脱离文档流,比如display:none,再修改属性,这里只发生一次回流。
-
尽量用transform来做形变和位移,不会造成回流
-
权衡速度的平滑。比如实现一个动画,以1个像素为单位移动这样最平滑,但reflow就会过于频繁,CPU很快就会被完全占用。如果以3个像素为单位移动就会好很多。
-
不要用tables布局的另一个原因就是tables中某个元素一旦触发reflow就会导致table里所有的其它元素 reflow。在适合用table的场合,可以设置table-layout为auto或fixed
-
避免不必要的复杂的CSS选择器,尤其是后代选择器(descendantselectors),因为为了匹配选择器将耗费更多的CPU
补充:
visibility:hidden
是隐藏元素,但元素仍占据着布局空间(即将其渲染成一个空框);display:none
将元素从渲染树中完全移除,元素既不可见,也不是布局的组成部分。display:none
会触发reflow,而visibility:hidden(隐藏)只会触发repaint,因为没有发生位置变化。
总结
重排:元素的尺寸变了、位置变了
重绘:元素的颜色、背景、边框、轮廓变了,但是,元素的几何尺寸没有变。
Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,其至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。