回流与重绘:解析前端性能优化的核心原理

142 阅读6分钟

问你一个问题:有没有一种标签,它既能让一个个小盒子实现行式布局,又能实现列式布局呢?

相信聪明的你瞬间就能想到,欸~,table标签!的确,它里面既能生成行,又能生成列,但是,它真的适合用来布局吗?

当你想要修改里面某一个单元格的大小时,会发现它周围的其他单元格也随之改变,在页面布局时,这显然不是我们想要的。

今天我将从为什么<table>不适合做页面布局开始,带你看回流与重绘的本质。


一、为什么 <table> 不适合做页面布局?

<table> 标签原本设计就是用于展示结构化的表格数据(如财务报表、数据统计表等),而非页面布局。

尽管它能快速实现列式布局(通过 <tr><td>),但其缺陷远大于优势:

缺点1. 语义化问题
使用 <table> 实现导航栏、侧边栏等页面结构,会混淆“数据”与“布局”的语义。搜索引擎可能误判页面为“表格数据”,影响 SEO,且对无障碍访问不友好(屏幕阅读器难以解析)。

缺点2. 性能陷阱
如果你想修改 <table> 中某个单元格的尺寸或位置,你会发现这个改变可能引发整个表格的全局回流(“火烧赤壁”效应),性能开销极大。

例如:当你调整 .sidebarwidth时,浏览器需重新计算所有行高和列宽,而对于页面越复杂的结构,其开销也是非常巨大的。

缺点3. 灵活性差
表格通过嵌套的结构(<table><tr><td>),限制了动态调整的可能性,响应式设计需额外 CSS 覆盖,维护成本高。


二、页面渲染流程:从 HTML 到像素的旅程

如果你想了解回流与重绘,那么,首先你要大致了解浏览器将 HTML、CSS 转化为页面的流程,详细内容可阅览《从url输入到页面渲染(二):解析和渲染过程详解》,以下为大致流程及其操作的概括:

  1. 构建 DOM 树:解析 HTML,生成 DOM(文档对象模型)。
  2. 构建 CSSOM 树:解析 CSS,生成 CSSOM(样式对象模型)。
  3. 构建渲染树(Render Tree) :结合 DOM 和 CSSOM,生成包含可见元素的渲染树。
  4. 布局(Layout) :计算元素的几何属性(位置、尺寸)。
  5. 分层(Layering):将页面划分为多个独立的图层并独立渲染。
  6. 绘制(Paint) :将元素绘制为像素。
  7. 合成(Composite) :将图层合成为最终画面。

其中,布局(Layout)绘制(Paint) 是性能优化的核心阶段,直接影响回流重绘的频率。


三、回流与重绘:性能优化的核心战场

1. 回流(Reflow)与重绘(Repaint)的定义

回流(Reflow) :当元素的几何属性(如位置、尺寸、布局模型)发生变化时,浏览器需要重新计算所有受影响元素的布局,并更新渲染树,回流是代价最高的操作,通常会触发后续的重绘。

重绘(Repaint) :当元素的外观属性(如颜色、背景色、可见性)发生变化时,浏览器只需重新绘制元素的外观,但无需调整布局,重绘的成本低于回流,但频繁操作仍会影响性能。

2. 回流与重绘的触发

回流的常见触发条件

  1. 页面首次渲染:浏览器初始化渲染树时需完成一次完整的布局计算(成本最高)。

  2. 窗口大小改变resize 事件触发时,页面布局可能需重新计算。

  3. 元素几何属性变化

    • 修改 widthheightmarginpadding 等属性。
    • 使用 transformopacity 等属性(若未启用硬件加速,则会触发回流)。
  4. DOM 节点操作

    • appendChildremoveChild 等动态修改 DOM 结构的操作。
    • 动态添加或移除样式类。
  5. 样式属性读取

    • 调用 getBoundingClientRect()offsetWidth 等 API 时,浏览器会强制触发回流以获取最新数据。

重绘的常见触发条件

  1. 修改非几何属性

    • colorbackground-colorborder-color 等。
    • visibility: hidden(元素仍占据空间)。
  2. 动态样式切换

    • 激活伪类(如 :hover)。
    • 动态添加/移除样式类。
特性回流(Reflow)重绘(Repaint)
触发条件元素的几何属性(尺寸、位置、布局模型)变化元素的外观属性(颜色、背景色、可见性)变化
性能开销极高(需重新计算布局并更新渲染树)较低(仅需重新绘制元素外观)
触发关系回流必然触发重绘重绘独立于回流
典型操作修改 widthheightmarginpaddingfloatdisplayposition 等修改 colorbackground-colorvisibilityopacity 等

四、从 <table> 到现代布局技术

看完上面的内容,相信你已经大致知道了<table> 布局为何触发全局回流 了吧。

没错,浏览器渲染表格时会遵循以下规则:

  • 表格渲染流程特殊:需等待所有单元格加载完成才能计算行高和列宽。
  • 局部修改破坏整体:调整某个单元格的尺寸(如 .sidebar 的 width),而这,都会触发整个表格的回流。

解决方法如下:

现代布局技术

1. 使用 Flexbox 与 Grid 进行布局

  • Flexbox:适用于一维布局(行或列),通过 flex-direction 控制方向。

    .container {
      display: flex;
      flex-direction: row; /* 行式布局 */
    }
    
  • Grid:适用于二维布局(行列均可定义),通过 grid-template-columns 和 grid-template-rows 定义网格。

    .container {
      display: grid;
      grid-template-columns: 200px 1fr;
    }
    

2. BFC(块级格式化上下文):隔离布局影响

通过 overflow: hiddenposition: absolute 创建 BFC,可避免浮动布局的塌陷问题,并减少回流范围。

.sidebar {
  overflow: hidden; /* 创建 BFC */
}

减少回流与重绘

1. 减少回流次数

  • 1. 批量操作 DOM:避免多次修改元素属性,改为一次性更新。

    // 低效写法:多次触发回流
    element.style.width = '100px';
    element.style.height = '200px';
    
    // 优化写法:使用 CSS 类批量修改
    element.classList.add('new-style');
    
  • 使用 CSS 动画替代 JavaScript 动画:CSS 动画由浏览器优化,可利用 GPU 加速。

    .animate {
      transition: transform 0.3s ease;
    }
    
  • 避免频繁读取布局属性:若需多次读取,先缓存结果。

    const width = element.offsetWidth; // 一次回流
    

2. 优化重绘

  • 合并样式修改:避免多次修改同一元素的外观属性。

    // 低效写法:多次触发重绘
    element.style.color = 'blue';
    element.style.backgroundColor = 'yellow';
    
    // 优化写法:合并为一次操作
    element.style.cssText = 'color: blue; background-color: yellow;';
    
  • 使用图层隔离:通过 will-change 或 transform 为元素创建独立图层,减少全局重绘。

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

本文为作者的理解,如果内容有误,欢迎各位读者在评论区指正。

如果觉得这篇文章对你有所帮助,不妨动动小手,点赞 + 收藏 一波!🌟

99db98bccf5c6a316b4efb1f323da9a0.gif