浏览器渲染页面的过程可以分为七个阶段:
1. HTML解析
浏览器接收到服务器响应的HTML文档后,会首先解析HTML文档,构建DOM树。DOM树是由DOM元素及属性节点组成的,树的根是document对象。
2. 样式计算
浏览器会解析CSS文件和元素上的inline样式,计算出DOM节点的样式。这个过程包括:
- 把CSS转换为浏览器能够理解的结构
- 计算出DOM节点的具体样式
3. 布局
布局阶段会计算出DOM树中可见元素的几何位置,这个过程叫做布局或重排。
4. 分层
浏览器会根据布局后的DOM树,将其分为多个图层。这个过程主要是为了处理页面中的复杂效果,如3D变换、页面滚动等。
5. 绘制
在分层的基础上,浏览器会为每个图层生成绘制列表,并将其提交到合成线程。
6. 分块
合成线程会将图层划分为图块,这些图块的大小通常是256x256或者512x512。
7. 光栅化
合成线程会按照视口附近的图块来优先生成位图,这个过程叫做光栅化。光栅化过程中,会将图块转换为位图。
回流(Reflow)和重绘(Repaint)
回流(Reflow)
当我们对DOM的修改引发了DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。
引起回流的操作包括:
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变
- 元素内容变化(文字数量或图片大小等等)
- 元素字体大小变化
- 添加或者删除可见的DOM元素
- 激活CSS伪类(例如::hover)
- 查询某些属性或调用某些方法
一些常用且会导致回流的属性和方法:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- scrollIntoView()、scrollIntoViewIfNeeded()
- getComputedStyle()
- getBoundingClientRect()
- scrollTo()
重绘(Repaint)
当我们对DOM的修改导致了样式的变化、但未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需要重新计算元素的几何属性、直接为该元素绘制新的样式。这个过程叫做重绘。
相比回流,重绘的性能开销较小,因为重绘只是重新绘制元素的外观,而不需要重新计算元素的位置。
为什么transform效率高
transform是通过创建一个新的复合图层来实现的,这个复合图层会单独进行绘制,然后再与其他图层进行合成。这样,当我们使用transform进行变换时,只需要重新绘制这个复合图层,而不需要重新绘制整个页面。
使用transform可以避免回流和重绘,因为它不会影响到DOM的布局,只会影响到最终的渲染结果。这就是为什么使用transform进行动画会比直接修改元素的top、left等属性效率高的原因。
优化策略
为了减少回流和重绘,我们可以采取以下策略:
-
批量修改DOM:使用DocumentFragment或者先将元素设为display: none,然后进行多次修改,最后再显示。
-
避免频繁读取会引发回流/重绘的属性:如果必须多次读取,就将值缓存起来。
-
使用transform和opacity来实现动画效果:这两个属性不会触发回流。
-
使用will-change属性:这个属性可以提前告知浏览器元素将要发生的变化,浏览器可以提前做好优化工作。
-
使用绝对定位或固定定位:绝对定位和固定定位的元素发生变化时,只会影响自身,不会影响其他元素。
通过理解浏览器的渲染原理和优化策略,我们可以编写出更高效的前端代码,提升用户体验。