重排必定会引发重绘,但重绘不一定会引发重排 重排(Reflow) && 重绘(Redraw)会付出高昂的性能代价
重绘
指的是当元素的样式(如颜色、背景、边框等)发生改变,但不影响其在文档流中的位置和大小时,浏览器会重新绘制(重新渲染)该元素,以更新其显示样式。重绘不会影响其他元素的布局和渲染,例如:修改元素的填充颜色,会触发重绘。
下面样式的修改会发生重绘:
- color
- border-style
- border-radius
- text-decoration
- box-shadow
- outline
- background
重排
指的是当元素的布局属性发生改变,例如改变元素的尺寸、位置、显示/隐藏等,会导致浏览器重新计算并应用所有受影响元素的几何属性(包括位置、尺寸等),然后进行重新布局。重新生成布局,重新排列元素(重新计算各节点和css具体的大小和位置:渲染树需要重新计算所有受影响的节点 例如:改元素的宽高,会触发重排
下面样式的修改会发生重绘:
- 页面初始渲染,这是开销最大的一次重排
- 添加/删除可见的DOM元素
- 改变元素位置
- 改变元素尺寸,比如边距、填充、边框、宽度和高度等
- 改变元素内容,比如文字数量,图片大小等
- 改变元素字体大小
- 改变浏览器窗口尺寸,比如resize事件发生时
- 激活CSS伪类(例如::hover)
- 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
- 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等
- 当我们调用getComputedStyl方法,或者IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个"即时性"和"准确性"
从范围来看
- 全局范围(全局布局) :从根节点html开始对整个渲染树进行重新布局;
- 局部范围(局部布局) :对渲染树的某部分或某一个渲染对象进行重新布局;
- 不难看出,我们应该尽量减少重排影响的范围
优化思路
样式集中改变(减少重排次数
)
当你试图对一个dom进行多次的样式修改的时候,不妨试着合并操作
上述操作有可能导致导致 3次重绘和1次重排,更常见的做法是通过动态的添加class,在一次操作里实现效果,从而减少重绘的可能
试着把DOM离线
当你试图对一个对象进行多次多种的操作,比如color、background、padding,marginLeft、marginTop的改动,不但是多次的还是多种多样的 我建议你可以把dom先进行离线操作,把它从渲染树上移走,直到你操作完毕,对隐藏的DOM元素操作不会引发其他元素的重排,这样只在隐藏和显示时触发2次重排
脱离文档流: 使用 absolute 或 fixed 脱离文档流
你可以试着将需要重排的元素,position属性设为absolute或fixed(某些特殊场合),减小重排范围
善用内存:在内存中多次操作DOM,再整个添加到DOM树(减小重排范围
)
在我最近做的一个demo里,我试着给我的切片上传增加动画,我想把每个切片的上传情况和编码显示在页面上
思路很自然,往预设的ul元素里不断丢li进去,这样100个盒子就放进去了,但是如果你是分100次丢进去,每次添加都可能触发重排,因为每个子元素的添加都会引起父元素的布局计算,当然这边不考虑现代浏览器的批量重排的优化方式,我们很难指望别人的优化手段,只能尽力的避免浪费
我采取的是ul也不先放容器里,而是用li把它喂饱之后再一次性把ul放到容器里
读写分离:将写入的值缓存,读取缓存的值
前面提到有一些浏览器针对重排会做出优化,当你触发重排的条件到达一定量的时候, 或者等到一定时间的时候,或者等一个线程结束,再一起推迟到浏览器下一次绘制的时候进行重排,但是当获取一些属性时,浏览器为取得正确的值也会触发重排,浏览器的优化在这里就无可奈何了
这些属性包括
- offsetWidth offsetHeight offsetTop ofsetLeft
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyle() (currentStyle in IE)。
如果你在代码里多次获取这些值,一次有多处用到,尽量避免每次用的时候都通过.style.offsetWidth获取,你应该试着在一次运行过程里通过缓存避免多次调用