重绘重排、CSS树DOM树渲染树、动画加速 ✅
浏览器下载完页面中的所有组件,HTML标记、JavaScript、CSS、图片后,之后会解析并生成两个内部数据结构:DOM树和渲染树。DOM树表示页面结构,渲染树表示DOM节点如何显示。
——《高性能JavaScript》
DOM树中的每一个需要显示的节点在渲染树中至少要存在一个对应的节点 (display为none的元素仍然存在于DOM树里面,你在控制台里面就能看见它,但是他并不存在于渲染树中;但是opacity为0的元素和visibility为hidden的元素虽然在屏幕上不可见,但是仍然存在于渲染树中,因为仍然在文档流中占有位置)。
DOM树&CSS树&渲染树
在DOM树和CSS树均构建完成后,浏览器会将它们结合起来生成渲染树。每个可见元素会在渲染树中对应一个节点,并且会包含与该元素相关的样式信息。
在渲染树构建完成后,浏览器会进行第一次绘制(Render)。这时,浏览器会将渲染树中的节点绘制为屏幕上的像素,也就是最终可视化的内容。
需要注意的是,CSS Tree的构建并不会阻塞DOM Tree的构建。当浏览器接收到HTML文档时,它会开始解析HTML并构建DOM树。同时,如果遇到外部CSS文件(例如通过link标签引入)或者内联CSS(通过style标签),浏览器会开始解析这些CSS内容以构建CSS树。在这种情况下,DOM树和CSS树的构建可以并行进行,直到浏览器需要应用样式时。但是如果css文件是通过script标签加载的 (应该行得通吧,我也没试过),默认情况下script标签会阻塞后续html代码的执行,此时可以认为“CSS Tree的构建阻塞了DOM Tree的构建”。如果我的css文件通过script来引入,但是script上存在defer属性,是否可以理解为“DOM Tree的构建阻塞了CSS Tree”?
重排&重绘
当DOM的变化影响了元素的集合属性(宽和高),浏览器需要重新计算元素的集合属性,同样其他元素的集合属性和位置也会受到影响,浏览器这个重新绘制的过程就是重排,完成重排后浏览器会重新绘制收到影响的部分到屏幕中,即重绘。
以下属性的改变可能会造成重排:height、width、margin、padding、border、position、top、left、right、bottom、display、visibility、font-size、font-family……
Q:为什么字体的大小和样式会造成重排?
A:可能会撑开容器的宽高
Q:绝对定位的元素脱离了文档流,为什么他们的left和top等属性被修改也会重排?
A:它的新位置需要根据其包含块重新计算,而且可能存在与其他元素的重叠。暂时只想到这么多
还有一些你“不经意间的操作”也会造成重排:
- 浏览器窗口尺寸的改变
- 添加或删除可见的DOM元素
- 通过JavaScript获取元素的offsetTop、scrollTop、clientTop、currentStyle等属性(浏览器对视图的更新“并不是实时的”,大多数浏览器通过队列化修改并批量执行来优化重排过程,如果通过Js获取元素的这些样式信息,浏览器会强刷新队列并要求计划任务立即执行,以获取最新的最准确的值)。
最小化重排与重绘
1、批量修改样式
比如可以把:
el.style.height = '1px'
el.style.margin = '2px'
el.style.padding = '3px'
修改为:
el.style = "height: 1px; margin: 2px; padding: 3px"
2、批量修改DOM
当需要对DOM元素进行一系列操作的时候,可以通过以下步骤来减少重排和重绘:
- 让元素脱离文档流(用得最多的是绝对定位、相对定位、浮动和设置display为none)
- 修改元素样式
- 让元素回归文档流
这里会触发重绘和重排的只有步骤1和步骤3。
上面这段分析来自《高性能JavaScript》。
一般情况下让元素脱离文档流(如设置为绝对定位),再修改他的样式,并不会直接影响其他元素的重排,因为他不再占据正常文档流的空间。其他情况确实没遇见过
但是当元素脱离了渲染树,那他的样式的改变确实就不会造成重排。所以可以设置display为none,以实现步骤1。
3、使用transform来代替left和top
先介绍一下合成层:现代浏览器中用于渲染的一个重要概念。它是一个独立的图层,用于将元素组合到一起以高效地进行绘制和显示。云里雾里的
合成层可以独立于原始文档流和元素的布局进行操作,允许浏览器在不影响其他元素的情况下进行更复杂的渲染处理。而且合成层的创建利用了图形处理单元(GPU)的能力来加速渲染。这使得元素能够以更高的性能进行动画和变换,尤其是在平移、缩放和旋转等操作时表现得尤为明显。
因为合成层可以独立处理,当在该层上的元素发生变化时(如位置、透明度等),浏览器只需重新绘制这个合成层,而不需要重新计算和更新整个文档流。这种方式可以显著提高网页的渲染效率,尤其是对于频繁更新的动画效果。 当使用 CSS transform 属性(如 translate)来移动元素时,浏览器会将该元素提升到一个新的合成层中。这意味着,浏览器可以独立于主布局流程处理这个元素,减少重新计算布局的次数。
所以可以认为:使用 transform 修改偏移量不会引起重排,因为它不会影响文档流中的其他元素的位置和布局。
动画加速
在CSS中启用动画加速,通常是通过使用硬件加速的技术实现的,动画加速本质上是触发了合成层。
可以通过以下属性来开启动画加速:
-
使用transform:通过改变元素的变换属性(如 translate、scale、rotate 等),浏览器可以将该元素提升到合成层,使用GPU渲染,从而实现更平滑的动画效果。
.animated { transition: transform 0.3s ease; } .animated:hover { transform: translateX(10px); /* 触发硬件加速 */ }
-
通过opacity:更改元素的不透明度(opacity)也能够触发合成层,从而启用硬件加速。
.fade-in { transition: opacity 0.5s ease; opacity: 0; } .fade-in.visible { opacity: 1; /* 触发硬件加速 */ }
-
避免重排属性:如果动画涉及到会引起重排(reflow)的属性,如 width、height、margin 等,应尽量避免使用这些属性来做动画,因为这会增加性能开销并影响流畅度。
-
使用will-change:通过CSS的 will-change 属性,开发者可以显式告诉浏览器某个元素即将发生变化。这样,浏览器可以提前进行优化,创建合成层。
(没用过这玩意,can i use上提示有兼容性问题?)
但是,硬件加速可能会造成内存占用的增加,当通过 transform 或 opacity 创建合成层时,浏览器会为这些元素分配更多的内存。而且如果每个动画元素都使用合成层,可能会导致GPU负担过重,导致整体性能下降,尤其是在低性能设备上。
此外,还存在一些兼容性问题 (没遇到过)
文章内容很多参考了《高性能JavaScript》。