1. 了解浏览器的重排和重绘
1.1 什么是浏览器的重排和重绘
在网页开发中,浏览器的渲染过程涉及两个重要概念:重排(Reflow)和重绘(Redraw) 。
- 重绘(Redraw) :当某些元素的外观改变,但不影响它在文档流中的位置时,触发重绘。这意味着浏览器会重新绘制受影响元素的外观,但不会改变它们的布局。
- 重排(Reflow) :当页面布局发生变化,影响了元素的位置和尺寸时,触发重排。这会导致整个渲染树的重新计算,涉及布局和几何属性的更新。
1.2 重排和重绘的关系
1.2.1 重排不一定导致重绘,但重绘一定会导致重排。
这意味着当我们只改变了元素的颜色、背景等外观属性时,只会触发重绘而不会引起整个页面布局的改变。但一旦改变了影响元素位置和尺寸的属性,将触发重排,进而导致重绘。
1.2.2 代价高昂的性能开销,需谨慎处理。
重排和重绘都会消耗大量的计算资源,特别是在频繁触发的情况下,会影响页面的性能和用户体验。因此,在前端开发中,需要谨慎处理操作,以减少不必要的重排和重绘。
2. 操作导致的重排和重绘
2.1 重绘的常见操作
某些操作会触发浏览器的重绘,即元素的外观发生变化,但不影响它们的布局。以下是常见导致重绘的操作:
- 修改颜色:修改元素的颜色属性(color、background、border-color等)会触发重绘。
- 调整边框样式:改变边框的样式(border-style)会导致重绘。
- 调整阴影效果:修改元素的阴影效果(box-shadow)会触发重绘。
- 文本修饰:对文本进行修饰,如修改文本装饰属性(text-decoration),会导致重绘。
2.2 重排的常见操作
重排涉及到页面布局的改变,通常由以下操作引起:
- 添加或删除可见的DOM元素。
- 改变元素的位置:修改元素的位置,例如通过改变定位属性(position)或调整浮动(float)。
- 改变元素的尺寸:包括边距、填充、边框、宽度和高度等属性的修改。
- 修改元素的内容:例如改变文字数量、图片大小等。
- 改变元素的字体大小。
- 改变浏览器窗口的尺寸,例如发生resize事件。
- 激活CSS伪类,如:hover。
- 查询某些属性或调用计算方法,如offsetWidth、offsetHeight等,以及getComputedStyle等。
2.3 重排影响的范围
2.3.1 全局范围(全局布局): 从根节点html开始对整个渲染树进行重新布局。
2.3.2 局部范围(局部布局): 对渲染树的某部分或某一个渲染对象进行重新布局。
3. 影响性能的重排和重绘操作
3.1 全局范围的重排
3.1.1 页面初始渲染
页面的初始渲染是开销最大的一次重排,涉及整个渲染树的构建和布局。
3.1.2 改变根节点 HTML
修改根节点HTML的任何部分都会触发全局重排,因为它影响了整个渲染树的结构和布局。
3.2 局部范围的重排
3.2.1 针对渲染对象的改变
对渲染对象进行操作可能导致局部范围的重排,这涉及到重新布局渲染树的一部分。这包括但不限于:
- 添加/删除可见的DOM元素
- 改变元素的位置
- 修改元素的尺寸,如边距、填充、边框、宽度和高度等
- 改变元素的内容,如文字数量、图片大小等
4. 重排的性能优化建议
4.1 样式集中改变
4.1.1 合并样式修改
在操作DOM时,建议集中进行样式修改,而不是逐个添加或修改属性值。这样可以减少重排次数。
xml
<!-- 示例HTML -->
<span id="demo">
我是demo
</span>
ini
// 示例JavaScript
const renderEle = document.getElementById('demo');
renderEle.style.color = 'red'; // 导致重绘
renderEle.style.background= '#ccc'; // 导致重绘
renderEle.style.padding = '15px 20px'; // 导致重排(重排会引起重绘)
可以改为:
javascript
// 优化后的JavaScript
document.getElementById('demo').className = 'demo'; // 添加class 统一添加/修改样式
css
/* 示例CSS */
.demo {
color: red;
background: #ccc;
padding: 15px 20px;
}
4.1.2 使用动态添加 class
动态添加class可以合并样式的修改,从而减少重绘次数。
4.2 将 DOM 离线
4.2.1 脱离文档流操作
在对DOM节点有较大改动时,可以先将元素脱离文档流,进行操作,然后再将其放回文档流。这可以减少重排次数。
xml
<!-- 示例HTML -->
<span id="demo">
我是demo
</span>
ini
// 示例JavaScript
const renderEle = document.getElementById('demo');
renderEle.style.display = 'none'; // 导致重排(重排会引起重绘)
renderEle.style.color = 'red'; // DOM不存在渲染树上不会引起重排、重绘
renderEle.style.background= '#ccc';// DOM不存在渲染树上不会引起重排、重绘
renderEle.style.padding = '15px 20px';// DOM不存在渲染树上不会引起重排、重绘
renderEle.style.display = 'block';// 导致重排(重排会引起重绘)
4.2.2 使用定位脱离文档流
将需要重排的元素,position属性设为absolute或fixed,减小重排范围。
xml
<!-- 示例HTML -->
<div id='demo'>
<span id="demo-one">
我是demo 1号
</span>
<span id="demo-two">
我是demo 2号
</span>
<span id="demo-there">
我是demo 3号
</span>
</div>
ini
// 示例JavaScript
const renderEle = document.getElementById('demo-one');
renderEle.style.position = 'fixed'; // 导致重排(重排会引起重绘)
renderEle.style.padding = '15px 20px'; // 导致重排(只有当前元素)
renderEle.style.height = '60px'; // 导致重排(只有当前元素)
5. 读写分离的优化策略
5.1 缓存写入的值
5.1.1 在写入值时进行缓存
在写入值的过程中,可以通过缓存的方式避免直接读取导致的重排,从而提高性能。
xml
<!-- 示例HTML -->
<span id="demo">
我是demo
</span>
ini
// 示例JavaScript
const offsetWidth = '100px';
const renderEle = document.getElementById('demo');
renderEle.style.offsetWidth = offsetWidth // 导致重绘(写入)
const tempoOffsetWidth = renderEle.style.offsetWidth // 读取可能会导致重排
可以优化为:
ini
// 优化后的JavaScript
const offsetWidth = '100px';
const renderEle = document.getElementById('demo');
renderEle.style.offsetWidth = offsetWidth // 导致重绘(写入)
const tempoOffsetWidth = renderEle; // 避免直接读取offsetWidth
5.1.2 读写分离优化性能
通过读写分离的方式,将写入和读取操作分开,避免直接读取导致的性能损耗。
这种优化策略适用于一些浏览器在获取属性时可能触发重排的情况,通过缓存写入的值,可以避免不必要的性能开销。