前言
大家好!在网页开发过程中,理解浏览器如何加载和显示页面内容是非常重要的。之前我们简单聊过一下从输入url到显示页面,浏览器都干了些啥,今天我们将深入探讨浏览器的渲染流程,特别是回流(Reflow)与重绘(Repaint)的概念,以及它们对性能的影响。这不仅对我们项目的开发中的性能优化有很大帮助,同时也是一道经典的面试题。通过了解这些概念及其触发条件,我们可以采取措施来减少不必要的回流和重绘,从而提高网页的响应速度和用户体验。
正文
浏览器加载资源后的渲染流程
当浏览器接收到一个HTML文档时,它会经历一系列步骤来解析并最终显示页面内容:
-
解析HTML代码:浏览器首先读取HTML文档,根据html文档的第一行去判断文档类型是否为html,并根据标签结构生成DOM树。这个其实和昨天聊到的虚拟dom被编译之后产生的树型对象差不多,感兴趣的读者姥爷可以去看看key:我与diff之间“说得清道得明”的关系 - 掘金
-
解析CSS:接着,浏览器解析所有相关的CSS样式表,生成CSSOM(CSS Object Model)树。
-
构建Render Tree:将DOM树和CSSOM树结合,去除不可见元素(如display: none;的元素),生成渲染树(Render Tree)。
-
计算布局(回流):根据渲染树进行布局计算,确定每个节点的具体位置和大小。
-
绘制页面(重绘):最后,GPU使用布局信息绘制页面,使用户能够看到最终结果。
什么是回流?
定义: 回流是指浏览器为了重新计算页面布局而执行的过程。这是一个相对昂贵的操作,因为它涉及到大量的计算。每当页面中的某个部分影响到布局时,浏览器就需要重新计算受影响区域内的所有元素的位置和尺寸。
触发因素:
-
改变窗口尺寸
-
改变元素尺寸(例如,设置宽度、高度)
-
增加或删除可见元素
-
页面初次渲染
-
修改元素的字体大小
-
添加或删除样式规则,尤其是那些影响布局的规则
每次发生回流时,浏览器都需要重新计算受影响区域内的所有元素的位置和尺寸,这可能会导致整个页面的重新布局。回流通常是一个耗时的操作,尤其是在复杂的布局中。
什么是重绘?
定义: 重绘是指将已经计算好布局的元素在屏幕上展示出来的过程。相比回流而言,重绘通常消耗较少的资源。重绘只是视觉效果的变化,不会影响到布局。
触发因素:
-
修改背景色
-
更换图片
-
修改颜色
-
设置透明度
-
修改边框样式
值得注意的是,回流是一定一定会导致重绘,因为一旦布局发生变化,页面上的视觉表现也需要随之更新;但是,重绘并不一定会引发回流,如果仅仅是颜色或背景图等非几何属性的变化,则不会影响到布局。这里我也给大家准备了一个小demo,帮助大家理解回流重绘的概念。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>回流与重绘</title>
<style>
.Reflow {
width: 100px;
height: 100px;
background-color: red;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
}
.Repaint {
width: 80px;
height: 80px;
background-color: blue;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
border: 10px solid green;
}
</style>
</head>
<body>
<div class="Reflow">回流</div>
<div class="Repaint">重绘</div>
<button id="ReflowBtn">触发回流</button>
<button id="ReRepaintBtn">触发重绘</button>
<script>
const ReflowBtn = document.getElementById('ReflowBtn')
const RepaintBtn = document.getElementById('ReRepaintBtn')
ReflowBtn.onclick = function () {
const div = document.querySelector('.Reflow')
const width = div.style.width
const height = div.style.height
const fontSize = div.style.fontSize
div.style.fontSize = fontSize === '15px' ? '30px' : '15px'
div.style.height = height === '100px' ? '200px' : '100px'
div.style.width = width === '100px' ? '200px' : '100px'
}
RepaintBtn.onclick = function () {
const div = document.querySelector('.Repaint')
console.log(div);
const color = div.style.backgroundColor
const opacity = div.style.opacity
const boredr = div.style.border
div.style.backgroundColor = color === 'blue' ? 'yellow' : 'blue'
div.style.opacity = opacity === '1' ? '0.5' : '1'
div.style.border = boredr === '10px solid rgb(0, 255, 47)' ? '10px solid blue' : '10px solid rgb(0,255, 47)'
}
</script>
</body>
</html>
如何减少回流/重绘
既然无论是回流还是重绘都会导致浏览器的性能消耗,而其中回流消耗的性能又明显要大于重绘。所以为了提升性能,我们需要尽量减少不必要的回流尤其是重绘操作。以下是一些有效的方法:
- 合理利用浏览器的优化策略:现代浏览器都具备一定的优化机制,比如渲染队列管理,可以批量处理多次样式更改请求,以减少实际发生的回流次数。
element.style.width = '100px';
element.style.height = '200px';
element.style.padding = '10px';
这段代码会触发三次回流,因为每次设置样式都会导致布局计算。但通过渲染队列机制,浏览器可能会将这三次样式更改合并成一次回流操作。
- 隐藏元素后再修改样式:对于需要大量样式修改的情况,可以先将该元素设置为display: none,完成所有改动后再将其设为可见,这样可以避免中间状态下的多次回流。
var element = document.getElementById('myElement');
// 首先设置为不可见
element.style.display = 'none';
// 修改元素的样式
element.style.width = '300px';
element.style.height = '300px';
element.style.backgroundColor = 'lightgreen';
element.style.borderRadius = '15px'; // 添加圆角
element.style.boxShadow = '5px 5px 15px rgba(0,0,0,0.3)'; // 添加阴影
// 最后设置回可见
setTimeout(function() {
element.style.display = 'block';
}, 0);
// 使用setTimeout将显示为block设置为宏任务,确保样式更新完成后再显示
-
使用 transform 和 opacity 进行动画:使用 transform 属性进行位移、旋转等变换,以及 opacity 属性控制透明度,都可以直接由GPU加速,而不引起回流。
-
避免频繁读取布局信息:获取某些布局信息(如 offsetWidth、offsetHeight 等)会强制浏览器立即执行当前的渲染队列。如果可能,尽量缓存这些值而不是反复读取。
-
减少复杂的选择器:过于复杂的选择器会增加浏览器匹配样式的时间,从而可能导致更多的回流。保持选择器简单可以帮助减少这一开销。
-
使用虚拟DOM:如果使用了像React或Vue这样的框架,那么它们内置的虚拟DOM机制可以在一定程度上帮助减少实际的DOM操作,从而减少回流和重绘。在我昨天的文章中有关于虚拟dom的详细介绍,有需要的可以看看。
-
vue中的v-show与v-if:这也是一个老生常谈的话题了,v-show本质上是控制元素的display属性,不论是否显示,dom都会占据文档流,但是v-if则是直接控制元素是否被添加到dom树中。所以在需要频繁切换是否显示的元素中是更推荐v-show去避免对dom树进行频繁操作的。
回流的强制执行
某些属性的获取操作,如offsetWidth、offsetHeight、offsetTop、offsetLeft、clientWidth、clientHeight、clientTop、clientLeft及scroll一系列属性,会强制浏览器立即执行当前的渲染队列。这意味着即使是在队列中等待的回流任务也会被立刻处理。所以在css中必须谨慎使用这类属性,尤其是在性能敏感的应用场景中。
结语
通过对浏览器渲染过程的理解,我们能够更加有效地识别和解决性能瓶颈。通过最小化回流和重绘的发生,我们可以显著提升网页的加载速度和流畅度。希望本文能帮助你更好地掌握相关知识,祝各位读者姥爷开发愉快,0 warning(s), 0 error(s)!