1. 浏览器的渲染流程

1)解析HTML,构建DOM树;

2)解析CSS,构建CSSOM树;

3)将DOM树和CSSOM树合并,生成渲染树(Render Tree),渲染树只包含可见节点及其样式信息;

4)布局/回流:计算每个可见节点在视口中的精确位置和大小;

5)绘制/重绘:将渲染树中的每个节点转换成屏幕上的实际像素,包括颜色、边框、阴影等。


2. 什么是回流/重排(Reflow)?

回流/重排是指当渲染树中的一部分(或全部)因为元素的规模、尺寸、结构、位置等发生变化,需要重新计算每个节点在视口中的几何属性(位置、尺寸)的过程。

触发回流的常见操作

1)添加或删除可见的DOM元素。

2)元素位置发生变化(如 positiontopleft 等)。

3)元素尺寸发生变化(如 widthheightpaddingbordermargin)。

4)内容改变(例如文本内容变化,或图片被不同尺寸的图片替换)。

5)浏览器窗口尺寸改变(resize 事件)。

6)激活CSS伪类(如 :hover)。

7)查询某些属性或方法,这些属性需要实时计算的几何信息,例如:

① offsetTopoffsetLeftoffsetWidthoffsetHeight

② scrollTopscrollLeftscrollWidthscrollHeight

③ clientTopclientLeftclientWidthclientHeight

④ getComputedStyle() 或 currentStyle

回流会重新计算布局,成本很高,因为它可能影响整个文档的部分或全部。


3. 什么是重绘(Repaint)?

重绘是指当元素的外观(如颜色、背景、可见性等)发生变化,但不会影响其几何属性,浏览器重新绘制该元素的过程。

触发重绘的常见操作

1)改变元素的 colorbackground-colorborder-colorbox-shadowoutline 等外观属性。

2)改变 visibility(但 display: none 会触发回流,因为元素从文档流移除)。

3)改变 opacity(在某些情况下可能触发硬件加速合成,不一定会重绘)。

重绘的代价相对较小,但仍然需要遍历渲染树进行绘制。


4. 回流与重绘的关系

1)回流必定导致重绘(因为位置变了,需要重新绘制)。

2)重绘不一定导致回流(如只改颜色)。

5. 如何避免和优化回流与重绘

为了提升页面性能,应尽量减少回流和重绘的次数,或者将它们的负面影响降到最低。

5.1 减少直接操作DOM和样式

1)批量修改样式:避免逐条修改样式,改用 class 或一次性设置 style.cssText

// 不推荐
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';

// 推荐
element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';
// 或使用
element.classList.add('new-style');

2)批量操作DOM:将多个操作合并在一起,减少对文档的频繁修改。

① 使用 DocumentFragment 在内存中构建子节点,然后一次性添加到DOM。

② 先隐藏元素(display: none),进行一系列操作后再显示。隐藏期间触发的回流只发生一次。

const ul = document.getElementById('list');
ul.style.display = 'none';
// 批量添加大量 li
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  ul.appendChild(li);
}
ul.style.display = 'block';

③ 使用 cloneNode 克隆一个节点,在克隆体上操作,然后替换原节点。

5.2 避免频繁读取引起布局抖动的属性

缓存需要多次读取的布局属性值,避免强制浏览器在每次读取时重新计算布局(即“强制同步布局”)。

// 不推荐——每次循环都触发回流
for (let i = 0; i < 100; i++) {
  console.log(div.offsetWidth);
}

// 推荐——缓存一次
const width = div.offsetWidth;
for (let i = 0; i < 100; i++) {
  console.log(width);
}
5.3 优化动画效果

1)使用 transform 代替 top/left 等位置属性transform 不会触发回流和重绘,而是由合成器处理,性能更优。

/* 不推荐 */
.box {
  animation: move 1s infinite;
}
@keyframes move {
  0% { left: 0; }
  100% { left: 100px; }
}

/* 推荐 */
.box {
  transform: translateX(0);
  animation: move 1s infinite;
}
@keyframes move {
  0% { transform: translateX(0); }
  100% { transform: translateX(100px); }
}

2)使用 opacity 配合 will-change,将动画元素提升到独立的合成层(浏览器会为其创建单独的层,减少重绘区域)。

.animated {
  will-change: transform, opacity;
}

3)对于高频事件(如 scrollresizemousemove,使用防抖(debounce)或节流(throttle)来减少触发频率。

window.addEventListener('resize', throttle(() => {
  // 执行操作
}, 100));
5.4 使用现代CSS布局技术

1)尽量使用 flexbox 或 grid 布局,它们在计算回流时通常比传统的 float 或定位方式更高效。

2)避免使用表格布局(table),因为即使一个单元格发生变化,也可能导致整个表格重新布局。

5.5 将频繁变化的元素提升到独立图层

除了 will-change,还可以使用 transform: translateZ(0) 或 backface-visibility: hidden 来强制创建新层。但不要过度使用,因为创建图层也会消耗内存。

5.6 其他细节

1)对于复杂动画元素,使用 position: fixed 或 absolute 将其脱离文档流,减少对其他元素的影响。

2)在需要动态修改样式时,尽量减少影响范围(例如通过修改父元素来统一改变子元素,而不是逐个修改)。


6. 总结

1)回流成本远高于重绘,应重点避免不必要的回流。

2)核心优化思路:减少布局计算次数、避免强制同步布局、利用合成器优势、批量操作DOM。

3)通过合理使用现代CSS属性(transformopacitywill-change)和JavaScript技巧,可以显著提升页面渲染性能。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐