今天在写打印弹出框的时候的卡顿引起的想法。
但那是 antd 与插件之间的显示问题(虽然与我也有很大关系,毕竟插件是我用的)。
什么是回流(重排)
回流(reflow),或叫做重排,当 render tree 中的一部分或全部元素的尺寸,布局,隐藏等改变而重新构建的过程就叫做回流。
每个页面至少需要一次回流,即页面第一次加载的时候。
什么是重绘
当 render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,而不影响布局时,则称为重绘(repaints)。
重绘不一定会回流,回流必然导致重绘。
何时发生
当页面布局和几何属性改变时,就会发生回流。
- 添加或删除可见的 DOM 元素;
- 元素位置改变;
- 元素尺寸改变,包括边距、填充、边框、宽度、高度;
- 内容改变;
- 页面渲染初始化;
- 浏览器矿口尺寸改变——resize 事件。
当然上面的每种情况都会进行重绘。但是在不影响布局的情况下就只会发生重绘而不会发生回流。
1 | const box = document.getElementById('box').style; |
不可见的元素不影响重排和重绘。
对于性能的影响
回流和重绘会不断发生,这是无可避免的。他们非常消耗资源,是导致网页性能低下的根本原因。
提高网页性能,就是要降低回流和重绘的成本,尽量少触发重新渲染。
现代浏览器做出了足够多的优化,会尽量把所有的变动集中在一起,拍成一个队列,然后一次性执行,尽量避免多次重新渲染。
1 | box.color = 'red'; |
尽管上面有两个样式变动,但是浏览器只会触发一次回流和重绘。
但是,在样式的写操作之后,如果有下面这些属性的读操作,都会引发浏览器的立即重新渲染。
- offsetTop / offsetLeft / offsetWidth / offsetHeight
- scrollTop / scrollLeft / scrollWidth / scrollHeight
- clientTop / clientLeft / clientWidth / clientHegiht
- getComputedStyle()
- getBoundingClientRect
一般规则:
- 样式表越简单,回流和重绘越快;
- 回流和重绘的 DOM 元素层级越高,成本就越高;
- table 元素的回流和重绘成本要高于 div 元素。
怎么避免回流重绘,优化(提高)性能
- DOM 的多个读操作应该放在一起,不要在两个读操作之间插入一个写操作。反之亦然;
- 如果某个样式是通过回流得到的,那么最好缓存结果。避免下次用到的时候又要回流;
- 不要一条一条改变样式,而是通过 class,或者 csstext 属性,一次性改变样式;
- 尽量使用离线 DOM,而不是真实的网面 DOM 来改变元素样式;
- 操作 Document Fragment 对象,完成后再把这个对象加入 DOM。
- 使用 CloneNode() 方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。
- 先将元素设为 display: none(一次回流和重绘),然后对这个节点进行多次操作,最后在恢复显示(一次回流和重绘)。这样就可以使用两次重新渲染代替多次重新渲染;
- position 属性为 absolute 或 fixed 的元素,重拍的开销会比较小,因为不用考虑对其他元素的影响;
- 只在必要的时候才将元素的 display 属性设置为可见,因为不可见的元素不影响回流的重绘。visibility: hidden 的元素支队重绘有影响,不影响回流;
- 使用虚拟 DOM 的脚本库,比如 React 等;
- 使用 window.requestAnimationFrame()、window.requestldleCallback() 这两个方法调节重新渲染。