回流(重排)和重绘—性能优化
回流(重排)和重绘—性能优化
[TOC]
写在前面
最近学习React,虚拟dom涉及这一点,而之前的工作中也曾涉及过,回流(重排)和重绘对于项目性能优化这一部分是必不可少的,这里对其做一下总结
浏览器渲染过程
想要了解其是什么,需要简单了解一下浏览器的形成过程
浏览器在渲染页面的时候,大致是以下几个步骤:
- 解析html生成DOM树,解析css,生成CSSOM树,将DOM树和CSSOM树结合,生成渲染树;
- 根据渲染树,浏览器可以计算出网页中有哪些节点,各节点的CSS以及从属关系 - 回流
- 根据渲染树以及回流得到的节点信息,计算出每个节点在屏幕中的位置 - 重绘
- 最后将得到的节点位置信息交给浏览器的图形处理程序,让浏览器中显示页面
回流(重排)
回流:英文叫reflow,指的是当渲染树中的节点信息发生了大小、边距等问题,需要重新计算各节点和css具体的大小和位置,有的将这一过程也称为重排。
容易造成回流的操作:
1、布局流相关操作
- 盒模型的相关操作会触发重新布局
- 定位相关操作会触发重新布局
- 浮动相关操作会触发重新布局
2、改变节点内的内容
改变节点的结构或其中的文本结构会触发重新布局
3、部分css
- width
- height
- padding
- border
- margin
- position
- top
- left
- bottom
- right
- float
- clear
- text-align
- vertical-align
- line-height
- font-weight
- font-size
- font-family
- overflow
- white-space
重绘
重绘:英文叫repaint,当节点的部分属性发生变化,但不影响布局,只需要重新计算节点在屏幕中的绝对位置并渲染的过程,就叫重绘。比如:改变元素的背景颜色、字体颜色等操作会造成重绘。
回流的过程在重绘的过程前面,所以回流一定会重绘,但重绘不一定会引起回流。
容易造成重绘操作的css:
- color
- border-style
- border-radius
- text-decoration
- box-shadow
- outline
- background
每次回流都会对浏览器造成额外的计算消耗,所以浏览器对于回流和重绘有一定的优化机制。浏览器通常都会将多次回流操作放入一个队列中,等过了一段时间或操作达到了一定的临界值,然后才会挨个执行,这样能节省一些计算消耗。但是在获取布局信息操作的时候,会强制将队列清空,也就是强制回流,比如访问或操作以下或方法时:
- offsetTop
- offsetLeft
- offsetWidth
- offsetHeight
- scrollTop
- scrollLeft
- scrollWidth
- scrollHeight
- clientTop
- clientLeft
- clientWidth
- clientHeight
- getComputedStyle()
这些属性或方法都需要得到最新的布局信息,所以浏览器必须去回流执行。因此,在项目中,尽量避免使用上述属性或方法,如果非要使用的时候,也尽量将值缓存起来,而不是一直获取。
减少回流和重绘
知道了定义,在日常开发中要尽量避免这些操作,但有时必要的要进行一些操作,如何处理呢,下面是一些优化方案
合并样式修改
减少造成回流的次数,如果要给一个节点操作多个css属性,而每一个都会造成回流的话,尽量将多次操作合并成一个,例:
1 | |
操作div的3个css属性,分别是padding、border、margin,此时就可以考虑将多次操作合并为一次。
方法一:使用style的cssText:
1 | |
方法二:将这几个样式定义给一个类名,然后给标签添加类名:
1 | |
批量操作DOM
当对DOM有多次操作的时候,需要使用一些特殊处理减少触发回流,其实就是对DOM的多次操作,在脱离标准流后,对元素进行的多次操作,不会触发回流,等操作完成后,再将元素放回标准流。
脱离标准流的操作有以下3中:
- 隐藏元素
- 使用文档碎片
- 拷贝节点
例:下面对DOM节点的多次操作,每次都会触发回流
1 | |
这样每次给ul中新增一个li的操作,每次都会触发回流。
方法一:隐藏ul后,给ul添加节点,添加完成后再将ul显示
1 | |
此时,在隐藏ul和显示ul的时候,触发了两次回流,给ul添加每个li的时候没有触发回流。
方法二:创建文档碎片,将所有li先放在文档碎片中,等都放进去以后,再将文档碎片放在ul中
1 | |
方法三:将ul拷贝一份,将所有li放在拷贝中,等都放进去以后,使用拷贝替换掉ul
1 | |
避免多次触发布局
如下回到顶部的操作:
1 | |
每隔20毫秒都会重新获取滚动过的距离,每次都会触发回流,代码优化如下:
1 | |
只获取一次,每次都让数字递增,避免每次都获取滚动过的距离。
对于页面中比较复杂的动画,尽量将元素设置为绝对定位,操作元素的定位属性,这样只有这一个元素会回流,如果不是定位的话,容易引起其父元素以及子元素的回流。