深度解析浏览器重排和重绘:优化前端性能的终极指南

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 读写分离优化性能

通过读写分离的方式,将写入和读取操作分开,避免直接读取导致的性能损耗。

这种优化策略适用于一些浏览器在获取属性时可能触发重排的情况,通过缓存写入的值,可以避免不必要的性能开销。

相关推荐
一个处女座的程序猿O(∩_∩)O几秒前
完成第一个 Vue3.2 项目后,这是我的技术总结
前端·vue.js
mubeibeinv1 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
逆旅行天涯8 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
m0_7482552629 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
长风清留扬1 小时前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
web147862107231 小时前
C# .Net Web 路由相关配置
前端·c#·.net
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖1 小时前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_748254881 小时前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl