回流、重绘的概念
页面渲染:
https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work#layout
回流与重绘:
https://segmentfault.com/a/1190000017329980/
何时触发回流、重绘
回流:
当页面布局和几何信息发生变化的时候。如增删DOM,元素位置变化,滚动条出现,窗口尺寸变化等。
重绘:
颜色,透明度等不影响布局的修改仅触发重绘。
回流一定触发重绘,重绘不一定回流。
浏览器优化机制
每次重排都要造成额外的计算消耗,因此现代浏览器会通过队列化修改并批量执行来优化重排过程。
浏览器会将修改操作放入队列,直到一段时间后或操作达到某个阈值,才清空队列。
但当你获取布局信息的操作的时候,会强制渲染队列刷新,导致回流重绘,因此尽量避免使用以下属性:
https://gist.github.com/paulirish/5d52fb081b3570c81e3a
减少回流、重绘
1. 最小化重绘和回流
当我们一定会导致回流时,最好的办法就是减少它发生的次数。可以合并对DOM多次的修改:
// 优化前:修改了三个样式属性,每一个都影响元素的几何结构// 导致三次回流const el = document.getElementById('test');el.style.padding = '5px';el.style.borderLeft = '1px';el.style.borderRight = '2px';// 优化后:仅一次回流// 使用cssTextconst el = document.getElementById('test');el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';// 修改CSS的classconst el = document.getElementById('test');el.className += ' active';
2. 批量修改DOM
当我们对DOM进行一系列修改时,可以通过以下步骤减少回流、重绘次数:
- 使元素脱离文档流
- 对其进行多次修改
- 将元素带回文档中
因此我们可以考虑以下方法:
- display 隐藏元素,修改后再重新展示
- 使用文档片段
document.createDocumentFragment()在当前DOM外构建一个子树,再拷贝回文档 - 将原始元素拷贝到一个脱离文档流的节点中,修改后再替换原始元素
但是现代浏览器大都会使用队列来储存多次修改,当你不去主动获取布局信息时,会等待js执行完成再进行回流与重绘。因此上述这些优化方法意义不大,但需理解。
3. 避免触发同步布局事件
上文提到,当我们访问元素一些属性时,会导致浏览器强制清空优化的队列,强制同步布局导致回流:
for (let i = 0; i < list.length; i++) {list[i].style.width = box.offsetWidth + 'px';}
上述代码存在着一个很严重的性能问题。每次循环时,都读取了 box.offsetWidth 。如果没有这一步,浏览器会在for循环过程中将操作都放入队列中,等js执行完后再进行重排。
但是为了获取到 box.offsetWidth ,浏览器必须先让上次循环中的样式更新生效,然后重新计算布局信息获取到 box.offsetWidth 的值。使得每一次的循环都强制浏览器刷新了队列。
我们可以优化成如下代码:
const boxWidth = box.offsetWidth;for (let i = 0; i < list.length; i++) {list[i].style.width = boxWidth + 'px';}
4. 复杂动画可以使用绝对定位让其脱离文档流
复杂的动画经常会引起回流重绘,因此我们可以使用绝对定位,让其脱离文档流,使影响面变小,提升性能。
5. css3硬件加速
将浏览器渲染过程交由GPU处理而不是浏览器自带的较慢的渲染器处理。
触发硬件加速的css属性:
- transform
- opacity
- filters
- Will-change
我们可以直接用 transform: translateZ(0); 写个空的transform 来开启css3硬件加速。
但注意不要滥用硬件加速:
- 使用过多会导致内存占用较大,出现性能问题
- GPU渲染字体会导致抗锯齿无效,动画结束时不关闭硬件加速,可能会产生字体模糊。
