性能优化 - 渲染优化

概念理解

回流(Reflow):当元素的布局属性(如宽度、高度、位置等)发生变化时,浏览器需要重新计算元素的几何属性,并重新布局页面,这个过程称为回流(页面首次渲染时叫做 Layout 布局)。

回流影响范围

  • 全局范围:从根节点开始,对整个渲染树进行重新布局。
  • 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局。

重绘(Repaint):当元素的外观属性(如颜色、背景、边框等)发生变化,但不影响布局时,浏览器只需要重新绘制元素,这个过程称为重绘。

记住:当触发回流时,一定会触发重绘,但是重绘不一定会触发回流。

触发回流的情况

  • 页面的首次渲染。
  • 浏览器的窗口大小发生变化(滚动条)。
  • 元素的内容发生变化。
  • 元素的尺寸或者位置发生变化:宽高、边框、切换定位、浮动、display。
  • 元素的字体大小发生变化:font-sizefont-familywhite-space
  • 查询某些属性或者调用某些 API:offset/client/scrollXXXgetComputedStyle,滚动 scrollTo()
  • 添加或者删除可见的 DOM 元素:appendChildinsertBeforeremoveChild

只触发重绘不会触发回流的属性

  • border-radius:边框圆角。
  • visibility:元素可见性。
  • box-shadow:盒子阴影。
  • color:文字颜色。
  • background-color:背景颜色。
  • outline:轮廓。
  • opacity:透明度(在合成层中)。

减少回流与重绘的措施

1. DOM 操作优化

  • 尽量在低层级的 DOM 节点进行操作:减少影响范围,避免触发大范围的回流。
  • 不要使用 table 布局:可能会使整个 table 进行重新布局。
  • 修改类名:而不是直接修改样式,通过类名切换批量修改样式。
  • 使用 absolute 或者 fixed:使元素脱离文档流,这样它们发生变化就不会影响其他元素。
  • 将元素先设置 display: none:操作结束后再把它显示出来。此时元素 DOM 操作不会引发回流和重绘。
  • 避免 CSS 表达式 :CSS 表达式(如 calc())会被频繁计算,导致性能问题,应避免使用。

2. 文档片段 DocumentFragment

DocumentFragment 文档片段接口,是没有父对象的最小文档对象,和正常的文档节点差不多,但在上面添加和删除节点,不会触发真实 DOM 树的重新渲染。

当 DocumentFragment 插入到文档中时,会一次性插入它所有的子孙节点,不会和直接操作 DOM 树一样引起频繁的重排和重绘。

javascript 复制代码
const fragment = document.createDocumentFragment();
// 应用所有 DOM 操作后再插入文档
for (let i = 0; i < 1000; i++) {
  const li = document.createElement("li");
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
document.getElementById("list").appendChild(fragment);

3. 渲染队列机制

将 DOM 的多个读操作或写操作放在一起,避免读写操作穿插着写。

浏览器存在渲染队列机制,执行写操作会先放一起累计,等到一定程度/触发读时再执行重排重绘。

javascript 复制代码
// ❌ 读写操作穿插
const width = element.offsetWidth; // 触发回流
element.style.width = width + 10 + "px"; // 写操作
const height = element.offsetHeight; // 触发回流
element.style.height = height + 10 + "px"; // 写操作

// ✅ 批量写操作
element.style.width = element.offsetWidth + 10 + "px";
element.style.height = element.offsetHeight + 10 + "px";

动画优化

1. 使用定位属性脱离文档流

使用 absolutefixed 脱离文档流,使动画元素不影响其他元素的布局。

2. 使用 transform 和 opacity

使用 transformopacity 进行动画,这些属性可以在合成层中由 GPU 处理。

浏览器会将使用这些属性的元素提升为合成层,在合成器线程中直接处理,具体触发渲染位置查看 浏览器帧渲染流程理解,无需重新计算布局和绘制,无需参与主线程的布局和重绘流程。

优势

  • transform:在合成层中处理,不会触发回流和重绘。
  • opacity:在合成层中处理,不会触发回流和重绘。

注意 :使用 transform3D 可以提升 GPU 合成层完成动画操作,但不宜过多合成层,会占用较大内存。

示例

css 复制代码
/* ❌ 可能使用会触发回流的属性 */
.element {
  animation: move 1s;
}
@keyframes move {
  from {
    left: 0;
  }
  to {
    left: 100px;
  }
}

/* ✅ 使用 transform 3D  */
.element {
  animation: move 1s;
}
@keyframes move {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(100px);
  }
}

3. 使用 will-change 提示浏览器

使用 will-change 属性提前告知浏览器元素将要发生变化,浏览器可以提前优化。

注意 :不过度使用 will-change,因为会创建新的合成层,占用内存。

css 复制代码
.element {
  will-change: transform;
}

其他

  • 仅渲染屏幕内容 :减少屏幕绘制内容,也可以达到优化效果。
    • 虚拟滚动:使用虚拟滚动插件,虚拟滚动本质是通过创建一个超大或符合展示所有数据的容器,用于模拟滚动效果,然后计算数据项的索引值,每次只渲染界面中出现的内容。
    • Intersection Observer:监听内容是否在屏幕内,是则渲染,不是则保留布局容器,其余隐藏,减少布局渲染压力。在渲染大量彩色文本内容,需要嵌套多层元素的时候非常有用。
    • content-visibility:auto :CSS 属性,可以实现离屏降低渲染压力的效果,搭配 contain-intrinsic-size 可以避免滚动条塌陷,具体参考 content-visibility

总结

  • 核心概念:回流会重新计算布局,重绘只重新绘制外观。回流一定会触发重绘,但重绘不一定触发回流。

  • 优化策略

    • DOM 操作优化:在低层级节点操作、使用类名切换、脱离文档流、批量操作。
    • 使用 DocumentFragment:批量 DOM 操作时使用,减少回流和重绘次数。
    • 渲染队列机制:批量读写操作,避免读写穿插。
    • 动画优化 :使用 transformopacity 进行动画,利用 GPU 合成层。
  • 注意事项

    • 避免使用 table 布局和 CSS 表达式。
    • 合理使用 will-change,避免创建过多合成层。
    • 优先使用只触发重绘的属性,避免触发回流。
相关推荐
laplace01232 小时前
Part 4. LangChain 1.0 Agent 开发流程(Markdown 笔记)
前端·javascript·笔记·python·语言模型·langchain
Jenlybein2 小时前
Git 仓库过滤敏感信息,通过配置 clean/smudge 过滤器的方式
前端·后端·github
其美杰布-富贵-李2 小时前
Spring Data Redis + Redisson 学习笔记
redis·学习·spring
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [fs]pidfs
linux·笔记·学习
woodykissme2 小时前
数控车刀片选型学习笔记
笔记·学习
微露清风2 小时前
系统性学习C++进阶-第十五讲-map和set的使用
java·c++·学习
航Hang*2 小时前
第十三章:网络系统建设与运维(高级)—— 路由控制和策略路由
运维·服务器·网络·笔记·ensp
千寻girling2 小时前
面试官 : “ 说一下 Vue 的 8 个生命周期钩子都做了什么 ? ”
前端·vue.js·面试
卖芒果的潇洒农民2 小时前
20250101输出 IAM学习笔记
笔记·学习