浏览器的渲染机制
在说渲染之前,我们首先需要了解一个概念:设备刷新率。
设备刷新率就是设备屏幕的渲染频率。通俗一点说就是,把屏幕当作一面墙,多久重新粉刷一次墙面。我们平常接触的设备,比如手机、电脑等,它们的默认刷新频率都是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)
- 查询某些属性或调用某些方法
如何减少重排
- 减少DOM的增删行为
比如你要删除某个节点,给某个父元素增加子元素,这类操作都会引起回流。如果要加多个子元素,最好使用DocumentFragment。
- 几何属性的变化
比如元素宽高变了,border变了,字体大小变了,这种直接会引起页面布局变化的操作也会引起重排。如果你要改变多个属性,最好将这些属性定义在一个class中,直接修改class名,这样只用引起一次回流。
- 元素位置的变化
修改一个元素的左右margin,padding之类的操作。所以在做元素位移的动画,不要更改margin之类的属性,使用定位脱离文档流后改变位置会更好。
- 获取元素的偏移量属性
例如获取一个元素的scrollTop、scrollLeft、scrollWidth、offsetTop、offsetLeft、offsetWidth、offsetHeight之类的属性,浏览器为了保证值的正确也会回流取得最新的值,所以如果你要多次操作,最好取完做个缓存。
- 页面初次渲染,这样的回流无法避免。
- 浏览器窗口尺寸改变,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,因为没有发生位置变化。