深入理解浏览器渲染机制:重排(Reflow)与重绘(Repaint)—— 上篇

150 阅读11分钟

摘要

在前端开发中,性能优化始终是一个核心议题。而浏览器渲染过程中的“重排(Reflow)”和“重绘(Repaint)”是影响页面性能的关键因素。它们是浏览器为了响应DOM或CSS的变化,重新计算元素几何属性和绘制像素的过程。不恰当的操作可能导致频繁的重排和重绘,从而造成页面卡顿、用户体验下降。本文将作为系列文章的上篇,以掘金博主的视角,从浏览器渲染的基础流程出发,深入剖析重排与重绘的定义、区别,以及它们与CSS布局(特别是BFC)的内在联系,旨在帮助读者建立对浏览器渲染机制的底层认知,为后续的性能优化打下坚实基础。

1. 页面是如何呈现在我们眼前的?—— 浏览器渲染流程概览

在深入探讨重排和重绘之前,我们首先需要理解一个网页从URL输入到最终呈现在屏幕上的完整过程。这个过程是复杂的,但我们可以将其简化为以下几个核心步骤:

  1. 处理输入与导航:用户在浏览器地址栏输入URL并回车,浏览器开始解析URL,并向服务器发起请求。

  2. 网络请求与响应:浏览器通过HTTP/HTTPS协议向服务器发送请求,服务器返回HTML、CSS、JavaScript、图片等资源。

  3. 解析HTML与构建DOM树(DOM Tree)

    • 浏览器接收到HTML字节流后,根据字符编码(如UTF-8)将其解析成HTML文本。
    • 接着,进行词法分析(Tokenizing),将HTML文本分解成一系列的Token(如<html><body><p>等)。
    • 然后,进行语法分析(Parsing),根据HTML的语法规则,将这些Token构建成一个树形结构,即DOM树(Document Object Model)。DOM树是文档的逻辑结构表示,每个节点都是一个DOM对象。
  4. 解析CSS与构建CSSOM树(CSS Object Model Tree)

    • 浏览器在解析HTML的同时,也会下载并解析CSS文件(或内联样式、<style>标签内的样式)。
    • CSS文本同样会经过词法分析和语法分析,最终构建成CSSOM树。CSSOM树包含了所有元素的样式信息,例如颜色、字体、大小等。
  5. 构建渲染树(Render Tree)

    • DOM树和CSSOM树是独立的,但它们共同决定了页面最终的视觉呈现。浏览器会将这两棵树合并,构建出渲染树(Render Tree)。
    • 渲染树只包含需要显示在页面上的元素(例如,display: none的元素不会出现在渲染树中),并且每个节点都包含了其对应的样式信息。
  6. 布局(Layout / Reflow / 回流 / 重排)

    • 一旦渲染树构建完成,浏览器就会开始计算渲染树中每个节点在屏幕上的确切位置和大小。这个过程被称为布局(Layout),也称为回流(Reflow)或重排(Layout)。
    • 布局阶段会确定所有元素的盒模型(Box Model),包括宽度、高度、内外边距、边框等,以及它们在视口中的精确坐标。
  7. 绘制(Paint / Repaint / 重绘)

    • 布局完成后,浏览器会根据布局阶段计算出的几何信息和渲染树的样式信息,将每个元素绘制到屏幕上。这个过程被称为绘制(Paint)或重绘(Repaint)。
    • 绘制会将元素的可见部分(如背景、颜色、边框、文本、阴影等)转换成屏幕上的像素。
  8. 合成(Compositing)

    • 在现代浏览器中,为了提高渲染效率,页面通常会被分成多个图层(Layer)。例如,z-index较高的元素、position: fixed的元素、使用transformopacity等CSS属性的元素,可能会被提升到独立的图层。
    • 图层的优势在于,当某个图层发生变化时,只需要重新绘制该图层,而不会影响其他图层,从而减少重绘和重排的范围。
    • 最后,浏览器会将所有图层合并(Compositing)在一起,显示在屏幕上。

2. 布局的基石:BFC与FFC

在布局阶段,浏览器会根据不同的“格式化上下文”(Formatting Context)来决定元素的布局方式。笔记中提到了“布局的难点:列式布局和理解BFC/FFC”,这正是理解布局底层原理的关键。

2.1 BFC(Block Formatting Context):块级格式化上下文

定义:BFC,即块级格式化上下文(Block Formatting Context),是Web页面中一个独立的渲染区域,它规定了内部块级盒子(Block Box)如何布局,并且这个区域的内部布局不会影响到外部,反之亦然。可以将其理解为一个独立的“小世界”,内部的元素只在自己的世界里进行布局,与外界互不干扰。

布局规则

  • 在BFC中,盒子(块级元素)会从包含块的顶部开始,在垂直方向上一个接一个地排列。
  • 相邻的块级盒子之间的垂直外边距(margin)会发生折叠(margin collapsing)。
  • BFC的区域不会与浮动元素(float box)重叠。
  • BFC会包含其内部的所有浮动元素(清除浮动)。

创建BFC的条件:以下CSS属性或元素可以创建新的BFC:

  1. 根元素<html>元素本身就创建了一个BFC。

  2. 浮动元素float属性不为none的元素(如float: left;)。

  3. 定位元素position属性为absolutefixed的元素。

  4. display属性为以下值的元素

    • inline-block
    • table-cell
    • table-caption
    • flex (创建FFC)
    • inline-flex (创建FFC)
    • grid (创建GFC)
    • inline-grid (创建GFC)
  5. overflow属性不为visible的元素:例如overflow: hidden;overflow: auto;overflow: scroll;

BFC的应用

  • 清除浮动:通过为父元素创建BFC(如设置overflow: hidden),可以使其包含内部的浮动子元素,避免父元素高度塌陷。
  • 防止外边距折叠:相邻的两个BFC之间不会发生外边距折叠。
  • 自适应两栏布局:利用BFC不与浮动元素重叠的特性,可以实现左侧浮动,右侧BFC的自适应布局。

2.2 FFC(Flex Formatting Context):弹性格式化上下文

定义:FFC,即弹性格式化上下文(Flex Formatting Context),是当一个元素的display属性设置为flexinline-flex时,为其内容创建的新的格式化上下文。在FFC中,子元素(flex items)会按照弹性盒模型(Flexbox)的规则进行布局。

布局规则

  • Flex容器内的子元素会成为flex items,并根据flex-directionjustify-contentalign-items等属性进行排列和对齐。
  • Flex items的margin不会折叠。
  • Flex items可以自动伸缩以填充可用空间或根据内容收缩。

与BFC的关系:一个flex容器(创建FFC的元素)本身也会创建一个BFC。这意味着flex容器的外部行为遵循BFC的规则,而其内部子元素的布局则遵循FFC的规则。

2.3 为什么表格(<table>)不适合做列式布局?

笔记中提到了<table>标签可以做列式布局,但为什么不推荐使用?

  1. 触发过多的回流和重绘<table>的布局特性决定了其内部的任何局部改变都可能导致整个表格的重新布局和绘制。例如,如果表格中的一个单元格内容发生变化,浏览器可能需要重新计算整个表格的列宽和行高,这会触发大范围的重排,性能开销巨大。
  2. 语义不符<table>标签在HTML中是用于展示表格数据的,例如财务报表、产品列表等。将其用于非表格数据的纯布局目的,会破坏HTML的语义化,不利于SEO和可访问性。
  3. 不够灵活:相较于CSS Grid或Flexbox,<table>在布局上的灵活性非常有限,难以实现复杂的响应式布局和动态调整。

因此,在现代Web开发中,我们通常使用Flexbox(弹性盒模型)或CSS Grid(网格布局)来实现复杂的列式布局,它们提供了更强大、更灵活、性能更好的布局能力。

3. 重排(Reflow)与重绘(Repaint)的定义与区别

重排和重绘是浏览器渲染过程中两个紧密相关但又有所区别的概念,它们是导致页面性能问题的两大元凶。

3.1 重排(Reflow / Layout / 回流)

定义:当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器需要重新计算这些元素在文档流中的位置和大小,并重新构建渲染树。这个过程被称为重排(Reflow),也称为回流(Layout)。

特点

  • 开销大:重排是一个非常耗时的操作,因为它涉及到浏览器重新计算整个或部分页面的布局。一旦发生重排,受影响的元素及其子元素、甚至祖先元素都可能需要重新计算。
  • 必然触发重绘:重排意味着元素的几何属性发生了变化,因此浏览器在重新布局之后,必然需要重新绘制这些元素,所以重排一定会导致重绘

触发重排的常见条件

  1. 页面首次渲染:当浏览器首次加载页面时,会进行一次完整的布局和绘制。这是最耗时的一次重排。

  2. 浏览器窗口大小改变:当用户调整浏览器窗口大小时,页面布局需要重新计算以适应新的视口尺寸。

  3. 元素尺寸或位置发生改变

    • 修改元素的widthheightpaddingmarginborder
    • 修改元素的lefttoprightbottom(当positionrelativeabsolutefixed时)。
    • 修改font-sizeline-heighttext-align等影响文本布局的属性。
    • 修改word-wrapwhite-space等。
  4. 元素内容的变化

    • 文本内容改变,或图片被替换,导致元素尺寸变化。
    • appendChildremoveChild等DOM操作,添加或删除可见的DOM元素。
  5. display属性的改变:例如从display: none变为display: block,这会使元素从不参与布局变为参与布局。

  6. 激活CSS伪类:例如:hover伪类改变了元素的几何属性(如widthheight)。

  7. 查询某些属性或调用某些方法时

    • 浏览器为了返回这些属性的最新值,需要强制进行一次同步布局,以确保返回的数据是最新的。这些属性和方法包括:

      • offsetTop, offsetLeft, offsetWidth, offsetHeight
      • scrollTop, scrollLeft, scrollWidth, scrollHeight
      • clientTop, clientLeft, clientWidth, clientHeight
      • getComputedStyle() (IE中是currentStyle)。
      • getBoundingClientRect():返回元素的大小及其相对于视口的位置。
    • 笔记中提到的img.getBoundingClientRect()就是一个典型的例子,它会强制浏览器立即执行一次重排,以获取最新的布局信息。

3.2 重绘(Repaint)

定义:当页面元素的样式发生改变,但这些改变不影响它在文档流中的位置和大小时,浏览器只需要重新绘制受影响的元素的外观。这个过程被称为重绘(Repaint)。

特点

  • 开销相对较小:重绘只涉及像素的重新绘制,不涉及布局的重新计算,因此性能开销比重排小。
  • 不一定触发重排:重绘不一定会导致重排,但重排一定会导致重绘。

触发重绘的常见条件

  • 修改元素的colorbackground-colorvisibility(从hiddenvisible)、outlinetext-decorationbox-shadow等。
  • 笔记中提到的colorbackground-colorvisibility: hidden(变为visible)就是典型的只触发重绘的属性。

4. 总结(上篇)

通过上篇的探讨,我们了解了浏览器从HTML到最终像素呈现的整个渲染流程,并深入理解了重排(Reflow)和重绘(Repaint)这两个核心概念。我们知道了重排是由于元素几何属性变化导致的布局重新计算,开销巨大且必然触发重绘;而重绘是元素外观变化导致的像素重新绘制,开销相对较小且不一定触发重排。此外,我们还回顾了BFC和FFC这些布局的基础概念,以及为什么传统的<table>布局在性能上存在劣势。

在下一篇中,我们将聚焦于如何识别和避免不必要的重排和重绘,以及在实际开发中,如何运用各种优化策略来提升页面的渲染性能,为用户带来更流畅的体验。