浏览器的渲染过程
- 解析HTML,形成DOM树,当遇到非阻塞资源时,如图片则会继续解析,当遇到css文件时,也会继续解析。当遇到js文件的阻塞渲染,会停止html的解析。
- 构建CSSOM树。浏览器将css规则转换为可以理解和使用的样式映射。浏览器遍历CSS中的每个规则集,根据CSS选择器创建具有父、子和兄弟关系的节点树。
- 第三步是将DOM和CSSOM组合成一个Render树,计算样式或渲染树从DOM树的根开始构建,遍历每个可见节点。像和它的子节点以及任何具有
display:none
样式的节点,例如script {display:none;}
这些标签将不会显示,也就是它们不会出现在Render树上。具有visibility:hidden
的节点会出现在Render树上,因为它们会占用空间。由于我们没有给出任何指令来覆盖用户代理默认值,因此上面代码示例中的script节点将不会包含在Render树中。 - 第四步是在渲染树上运行布局以计算每个节点的几何体。(回流/重排)。在此基础上,考虑到视区大小,浏览器将确定屏幕上所有不同框的尺寸。以视区的大小为基础,布局通常从body开始,用每个元素的模型属性排列所有body的子孙元素的尺寸,为不知道其尺寸的替换元素(例如图像)提供占位符空间。
- 最后一步是将各个节点绘制到屏幕上。
回流:
以视区的大小为基础,布局通常从body 开始,用每个元素的框模型属性排列所有的body的子孙元素的尺寸,为不知道其尺寸的替换元素(例如图像)提供占位符空间。
总之,就是计算它们在设备视口(viewport)内的确切位置和大小(也就是几何属性和位置),这个计算的阶段就是回流。
重绘:
我们可以将渲染树上的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘节点。也就是在屏幕上绘制图形,但是如果背景颜色发生改变,那么会引起重绘,但是不会发生回流。
回流一定会触发重绘,重绘不一定会触发回流。
触发回流重绘的操作
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边距、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
- 页面一开始渲染的时候(这肯定避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
浏览器的优化机制
由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一定阈值,才会清空队列。但是,当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scroolTop、scroolLeft、scroolWidth、scroolHeight
- clientTop、clientLeft、clientWidth.clientHeight
- getComputedStyle()
- getBoundingClientRect
因此我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。
减少回流和重绘的方法:
- 把多个DOM操作合并在一起,使用csstext或者class
- 使用cssText
- 修改css的class
- 使得节点脱离文档流(也就是不可见的节点),操作,回到文档流。
- 使用
display:none
- 必须触发布局事件
也就是offset。如果多次用到同一个offset,那么可以先把它的值保存起来再使用。 - 对于比较复杂的动画可以使用绝对定位使其脱离文档流。对于复杂动画效果,由于经常会引起回流重绘,因此,我们可以使用绝对定位使其脱离文档流,否则会引起父元素以及后续元素频繁的回流。