性能优化之重绘回流

上一篇博客在介绍浏览器请求资源的过程中提及了重绘和回流,也对重绘回流的概念进行了介绍。根据概念,至少会有一次回流和重绘发生在第一次页面加载时,且回流一定会导致重绘,重绘不一定引起回流.

要进行性能优化,我们就必须搞清楚什么情况会导致回流和重绘,其中回流是重点,因为它的开销会比重绘高出很多。

回流何时发生

当页面布局和集合属性发生变化时就需要回流,常见的有以下情况:

  • 添加或者删除可见DOM元素
  • 元素位置改变
  • 元素尺寸改变
  • 文本改变
  • 图片(没有固定高度)加载src
  • 页面初始化渲染
  • 浏览器窗口尺寸改变(resize)
  • 操作class属性
  • 脚本操作DOM
  • 计算offsetWidth和offsetHeight属性
  • 设置style属性的值

浏览器队列

当我们在js中操作dom的一些样式是,通常都会引起页面的重绘和回流,如果每次一引起回流重绘浏览器就重新去渲染的话,这样会耗费大量的时间。所以很多浏览器会维护1个队列,里面存放着回流和重绘的操作,等队列满了或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理,所有的重绘回流就变成了一次。

但有时我们的代码会引起浏览器提前flush队列,比如,当我们向浏览器请求以下style信息时,就会提前让浏览器flush队列:

  • offsetTop,offsetLeft,offsetWidth,offsetHeight
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • width,height
  • 请求了getComputedStyle()或者IE的currentStyle

原因:
请求以上这些值时,浏览器需要清空队列,计算出最新的元素尺寸和位置样式信息(重绘回流),因为浏览器认为队列中的某些操作会造成我们获取的值并不是最精确的!

优化方法

在没有维护队列的浏览器中,减少重绘回流就需要我们合并样式的修改,尽量一次渲染到位,而有优化策略的浏览器,我们就要好好的利用这一点,减少会提前flush队列的操作

1.将多个样式修改放到一个class中或者通过classText一次性修改:

1
2
3
4
5
6
7
8
9
//bad
var left = 1;
var top = 1;
el.style.left = left + 'px';
el.style.top = top + 'px';
//good
el.className += 'className1';
//good
el.style.cssText += 'left:'+ left + 'px;top:' + top + 'px;'

2.避免访问会引起flush队列的属性,如要访问,利用缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//bad
for(){
el.style.left = el.offsetLeft + 5 + 'px';
el.style.top = el.offsetTop + 5 + 'px';
}
//good
var left = el.offsetLeft,top=el.offsetTop;
s = el.style;
for(){
left += 10;
top +=10;
s.left = left + 'px';
s.top = top + 'px';
}

3.动画效果应用到脱离文档流的元素上
元素脱离了文档流之后,不会影响其他元素的布局,所以只会导致重绘,可以减少开销

4.避免使用css表达式

5.让要操作的元素进行”离线处理”,处理完后一起更新

a) 使用DocumentFragment进行缓存操作,引发一次回流和重绘;
b) 使用display:none技术,只引发两次回流和重绘;
c) 使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;