很重要的前端基础知识。面试的时候没回答上来,为此直接寄寄了。
说回正事:回流和重绘是前端很重要的基础知识,和性能优化息息相关。
-
什么是回流?
回流也可以叫重排:可以理解为对元素重新排列?
我们都知道浏览器根据我们写的代码计算过样式之后,将计算结果渲染到页面上,每一次重新渲染都会造成回流(重新排列,重新组合页面)。
常见的重排原因:
- 添加或者删除DOM: append remove
- 改变元素的尺寸 : 改变盒子模型
- 改变元素内容:比如内容content变大,把盒子撑开?
- 页面渲染初始化:首次加载页面
- 窗口尺寸变化: 重新计算内容大小、位置。
- 计算布局信息:Js查询某些布局的信息,如
offsetWidth
、offsetHeight
、scrollTop
、scrollLeft
等时,浏览器需要重新计算布局以返回最新的值,这是因为这些属性依赖于当前的布局信息。
全局范围
全局范围的重排对性能的消耗很大,比如首次加载页面。渲染主线程要重新计算渲染DOM树。
局部范围
局部范围的重排相对影响较小。
-
什么是重绘?
当我们修改DOM样式,不改变DOM元素的几何属性,浏览器不需要重新计算元素的几何属性,直接为该元素绘制新的样式。这个过程叫做重绘(几何不变,重新绘画)。
常见重绘的原因:
- 改变外观属性:比如color、bgc、border-color
- 透明的变化: opacity
- css滤镜:filter
- 动画和过渡
- 视频播放
-
回流与重绘的关系
- 回流必然重绘,重绘不一定回流。
- 回流性能消耗比重绘大。
-
如何减少回流与重绘(优化)
-
js修改样式时,尽量使用动态className(样式集中操作)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
background-color: red;
transition: all 0.5s;
}
.in {
width: 100px;
height: 100px;
background-color: blue;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
box.addEventListener('mouseenter', () => {
box.style.width = '100px'
box.style.height = '100px'
box.style.backgroundColor = 'blue'
})
</script>
</body>
</html>
上面这种方法,在每一次用js修改样式的时候都会造成回流(宽高)和重绘(颜色)。共计:两次回流,一次重绘。
如果改成动态添加类名就可以减少回流重绘次数,变成一次。
<script>
const box = document.querySelector('.box')
box.addEventListener('mouseenter', () => {
/* box.style.width = '100px'
box.style.height = '100px'
box.style.backgroundColor = 'blue' */
box.classList.add('in')
})
</script>
-
优化动画
比如,一个动画效果:向右移动100px
- 如果每次移动1px,换平滑,看上去很丝滑,但是就要移动100次,这就造成了100次的动画效果,及其耗费GPU资源。
- 如果每次移动3px,假设肉眼看上去一样丝滑(对计算机来说都是以.....ms计算的),这样,同样的效果只计算了33次,降低3倍资源消耗。
-
使用GPU加速
如果实在减少不了重排和重绘,就只能调用GPU加速,术业有专攻。
- 硬件加速:通过CSS的
transform
、opacity
、filter
等属性,可以创建一个新的复合层(Compositor Layer),这些层的动画会由GPU处理,从而减少主线程的工作量。例如,使用transform: translate3d()
代替top
和left
来移动元素。 - will-change属性:提前告知浏览器某些元素将会发生变化,这样浏览器可以提前做好优化准备,创建一个新的复合层。例如,
will-change: transform;
。 - 使用CSS动画:CSS动画通常比JavaScript动画更高效,因为它们可以在浏览器的复合线程上运行,而不占用主线程。
- 避免布局颠簸(Layout Thrashing) :在连续的DOM操作中,尽量减少对布局信息的读取和写入,以免引发多次不必要的回流。
- 使用requestAnimationFrame:将DOM操作和样式更新放在
requestAnimationFrame
回调中,这样可以确保这些操作在下一帧动画开始前执行,减少不必要的回流和重绘。 - 利用Flexbox和Grid布局:这些现代布局模型比传统的盒模型更高效,可以减少回流和重绘的次数。
- 减少复杂度:简化CSS选择器,避免使用复杂的阴影和渐变,减少重绘区域的面积。
- 管理图层:通过合理地管理图层,将不经常变化的元素放在单独的图层上,可以减少整个页面的重绘次数。
- 使用Web Workers:对于一些复杂的计算和数据处理,可以使用Web Workers在后台线程中执行,避免阻塞主线程,减少回流和重绘。
- 监控性能:使用开发者工具的性能监控面板来分析页面性能,找出导致回流和重绘的原因,并针对性地进行优化。