CSS:浏览器的重排和重绘

116 阅读5分钟
浏览器的渲染机制

在说渲染之前,我们首先需要了解一个概念:设备刷新率。

设备刷新率就是设备屏幕的渲染频率。通俗一点说就是,把屏幕当作一面墙,多久重新粉刷一次墙面。我们平常接触的设备,比如手机、电脑等,它们的默认刷新频率都是60FPS,也就是屏幕在1s内渲染了60次,约16.7ms渲染一次屏幕。

这就意味着,我们的浏览器最佳的渲染性能就是所有的操作都在一帧16.7ms内完成。能否做到这一点直接影响着页面渲染效果和用户交互。

浏览器渲染流程

从网络拿到所请求的数据后:

  • 解析html、xhtml、svg这三类文档,形成dom树;
  • 解析css,形成css规则树;
  • 解析js,js有可能通过api操作dom树和css规则树。

解析完成后,浏览器引擎会合并dom树和css规则树,构建渲染树Rendering Tree。注意:

  • 渲染树和dom树并不完全相同,比如,像header元素和设置了display:none的元素就不在渲染树中,但是设置了visibility:hidden的元素仍会显示在其中。

在渲染树构建完成后,

  • 浏览器会对这些元素进行定位和布局,也就是重排(回流)reflow或者layout。
  • 浏览器绘制这些元素的样式、颜色、背景、大小及边框等,也就是重绘repaint。
  • 然后浏览器会将各层的信息发送给GPU,GPU会将各层合成显示在屏幕上。
重排(回流)reflow

重排是指重新计算页面的布局。某个节点重排时会重新计算节点的尺寸和位置,而且还有可能触发其子节点、祖先节点或页面上的其他节点重排。重排之后会再触发一次重绘(repaint)。

当渲染树中的一部分或全部因为元素的尺寸、布局、隐藏等属性的改变,而需要重新构建渲染树,这就是重排。每个页面至少需要一次重排,也就是在页面第一次加载的时候。

导致重排的操作
  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如::hover)
  • 查询某些属性或调用某些方法
如何减少重排
  1. 减少DOM的增删行为

比如你要删除某个节点,给某个父元素增加子元素,这类操作都会引起回流。如果要加多个子元素,最好使用DocumentFragment。

  1. 几何属性的变化

比如元素宽高变了,border变了,字体大小变了,这种直接会引起页面布局变化的操作也会引起重排。如果你要改变多个属性,最好将这些属性定义在一个class中,直接修改class名,这样只用引起一次回流。

  1. 元素位置的变化

修改一个元素的左右margin,padding之类的操作。所以在做元素位移的动画,不要更改margin之类的属性,使用定位脱离文档流后改变位置会更好。

  1. 获取元素的偏移量属性

例如获取一个元素的scrollTop、scrollLeft、scrollWidth、offsetTop、offsetLeft、offsetWidth、offsetHeight之类的属性,浏览器为了保证值的正确也会回流取得最新的值,所以如果你要多次操作,最好取完做个缓存。

  1. 页面初次渲染,这样的回流无法避免。
  1. 浏览器窗口尺寸改变,resize事件发生也会引起回流。
重绘repaint

当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的可见性、颜色、边框等外观效果,而不会影响布局,就叫做重绘。

一些重排重绘的优化方案
  • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
  • 减少不必要的DOM层级。改变DOM树中的一级会导致所有层级的改变,上至根部,下至被改变节点的子节点。这导致大量时间耗费在执行重排上面。
  • 尽可能的为产生动画的HTML元素使用fixed或absolute的position,那么修改他们的CSS就不会触发重排。
  • img标签要设置高宽,以减少重绘重排。
  • 把DOM离线后修改,如将一个dom脱离文档流,比如display:none,再修改属性,这里只发生一次重排。
  • 尽量用transform来做形变和位移,不会造成重排。
  • 避免不必要的复杂的CSS选择器,尤其是后代选择器(descendantselectors),因为为了匹配选择器将耗费更多的CPU。
  • 避免频繁读取会引发重排的属性,如果确实需要多次使用,就用一个变量缓存起来。

补充:

visibility:hidden 是隐藏元素,但元素仍占据着布局空间(即将其渲染成一个空框);

display:none 将元素从渲染树中完全移除,元素既不可见,也不是布局的组成部分。

display:none 会触发reflow,而visibility:hidden 只会触发repaint,因为没有发生位置变化。