调试布局偏移

了解如何识别和修正布局偏移。

凯蒂·亨佩纽斯
Katie Hempenius

本文第一部分介绍用于调试布局偏移的工具,而第二部分则探讨在确定布局偏移的原因时应考虑的思路。

工具

布局不稳定 API

Layout Instability API 是用于衡量和报告布局偏移的浏览器机制。用于调试布局偏移的所有工具(包括开发者工具)最终都是基于 Layout Instability API 构建的。不过,由于 Layout Instability API 的灵活性,直接使用 Layout Instability API 是一种强大的调试工具。

用法

衡量 Cumulative Layout Shift (CLS) 的同一代码段也可以用于调试布局偏移。以下代码段会将有关布局偏移的信息记录到控制台。通过检查此日志,您可以了解发生布局偏移的时间、地点和方式。

let cls = 0;
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift', buffered: true});

运行此脚本时,请注意以下事项:

  • buffered: true 选项表示 PerformanceObserver 应检查浏览器的性能条目缓冲区,以找出在观察器初始化之前创建的性能条目。因此,PerformanceObserver 会报告在初始化之前和之后发生的布局偏移。在检查控制台日志时,请谨记这一点。最初的布局偏移过多可能反映的是报告积压量,而不是突然发生的大量布局偏移。
  • 为了避免影响性能,PerformanceObserver 会等到主线程空闲时再报告布局偏移。因此,根据主线程的繁忙程度,发生布局偏移的时间与控制台中记录的时间之间可能会略有延迟。
  • 此脚本会忽略用户输入后 500 毫秒内发生的布局偏移,因此不会计入 CLS。

系统会组合使用以下两个 API 来报告有关布局偏移的信息:LayoutShiftLayoutShiftAttribution 接口。以下各部分更详细地介绍了其中每个接口。

LayoutShift

系统使用 LayoutShift 接口报告每次布局偏移。条目的内容如下所示:

duration: 0
entryType: "layout-shift"
hadRecentInput: false
lastInputTime: 0
name: ""
sources: (3) [LayoutShiftAttribution, LayoutShiftAttribution, LayoutShiftAttribution]
startTime: 11317.934999999125
value: 0.17508567530168798

上面的条目表示的是布局偏移,在此期间,3 个 DOM 元素的位置发生变化。这次布局偏移的得分为 0.175

以下是与调试布局偏移最相关的 LayoutShift 实例的属性:

媒体资源 说明
sources sources 属性列出了在布局偏移期间移动的 DOM 元素。此数组最多可包含五个来源。如果受布局偏移影响的元素超过五个,系统会报告影响布局偏移的五个最大(根据对布局稳定性的影响进行衡量)的来源。此信息使用 LayoutShiftAttribution 界面进行报告(下文有详细介绍)。
value value 属性会报告特定布局偏移的布局偏移得分
hadRecentInput hadRecentInput 属性用于指示是否在用户输入后 500 毫秒内发生了布局偏移。
startTime startTime 属性指示发生布局偏移的时间。startTime 以毫秒为单位表示,并且是根据网页加载的启动时间进行衡量的。
duration duration 属性将始终设置为 0。此属性继承自 PerformanceEntry 接口(LayoutShift 接口扩展 PerformanceEntry 接口)。不过,时长的概念不适用于布局偏移事件,因此它设置为 0。如需了解 PerformanceEntry 接口,请参阅spec

LayoutShiftAttribution

LayoutShiftAttribution 接口描述了单个 DOM 元素的单次偏移。如果多个元素在布局偏移期间发生偏移,则 sources 属性会包含多个条目。

例如,以下 JSON 对应于具有一个来源的布局偏移:<div id='banner'> DOM 元素从 y: 76 向下偏移到 y:246

// ...
  "sources": [
    {
      "node": "div#banner",
      "previousRect": {
        "x": 311,
        "y": 76,
        "width": 4,
        "height": 18,
        "top": 76,
        "right": 315,
        "bottom": 94,
        "left": 311
      },
      "currentRect": {
        "x": 311,
        "y": 246,
        "width": 4,
        "height": 18,
        "top": 246,
        "right": 315,
        "bottom": 264,
        "left": 311
      }
    }
  ]

node 属性用于标识发生偏移的 HTML 元素。在开发者工具中将鼠标悬停在此属性上,会突出显示相应的页面元素。

previousRectcurrentRect 属性会报告节点的大小和位置。

  • xy 坐标分别报告元素左上角的 x 坐标和 y 坐标。
  • widthheight 属性分别报告元素的宽度和高度。
  • toprightbottomleft 属性会报告与元素给定边缘对应的 x 或 y 坐标值。换句话说,top 的值等于 ybottom 的值等于 y+height

如果 previousRect 的所有属性都设置为 0,则表示该元素已切换到视图中。如果 currentRect 的所有属性都设置为 0,则表示该元素已移出视图。

在解释这些输出时,需要了解的最重要的一点是,列为“来源”的元素是在布局偏移期间发生变化的元素。不过,这些元素有可能只是与布局不稳定的“根本原因”间接相关。以下是几个示例。

示例 1

系统将通过一个来源(元素 B)报告此布局偏移。不过,这种布局偏移的根本原因是元素 A 的大小发生了变化。

展示因元素尺寸变化而导致的布局偏移的示例

示例 2

此示例中的布局偏移会通过两个来源进行报告:元素 A 和元素 B。这种布局偏移的根本原因是元素 A 的位置发生变化。

示例:元素位置变化导致的布局偏移

示例 3

此示例中的布局偏移会通过一个来源进行报告:元素 B。更改元素 B 的位置导致了这种布局偏移。

示例:元素位置变化导致的布局偏移

示例 4

虽然元素 B 会改变大小,但在此示例中没有布局偏移。

显示元素会改变大小但不导致布局偏移的示例

观看演示,了解 Layout Instability API 如何报告 DOM 更改

DevTools

性能面板

开发者工具 Performance 面板的 Experience 窗格会显示在给定性能轨迹期间发生的所有布局偏移,即使这些偏移发生在用户互动的 500 毫秒内,因此不会计入 CLS。 将鼠标悬停在 Experience 面板中的特定布局偏移上,可突出显示受影响的 DOM 元素。

开发者工具 Network 面板中显示的布局偏移的屏幕截图

如需查看有关布局偏移的详细信息,请点击布局偏移,然后打开 Summary 抽屉式导航栏。对元素尺寸所做的更改会按照 [width, height] 格式列出;对元素位置所做的更改会以 [x,y] 格式列出。Had recent input 属性用于指明是否在用户互动后 500 毫秒内发生了布局偏移。

开发者工具“Summary”标签页的布局偏移的屏幕截图

如需了解布局偏移的持续时间,请打开 Event Log 标签页。 也可以在 Experience 窗格中查看红色布局偏移矩形的长度,来大致估算布局偏移的持续时间。

开发者工具“事件日志”标签页的屏幕截图,其中显示布局偏移

如需详细了解如何使用“性能”面板,请参阅性能分析参考

突出显示布局偏移区域

突出显示布局偏移区域有助于快速一目了然地了解网页上发生的布局偏移的位置和时间。

如需在开发者工具中启用布局偏移区域,请依次转到 Settings > More Tools > Rendering > Layout Shift Regions,然后刷新您要调试的页面。 布局偏移的区域会短暂突出显示为紫色。

确定布局偏移原因的思考流程

无论布局偏移的时间或方式如何,您都可以按照以下步骤找出导致布局偏移的原因。您可以用运行 Lighthouse 来补充这些步骤 - 不过请注意,Lighthouse 只能识别初始页面加载期间发生的布局偏移。此外,Lighthouse 也只能针对布局偏移的某些原因(例如没有明确宽度和高度的图片元素)提供建议。

确定布局偏移的原因

以下事件可能导致布局偏移:

  • DOM 元素的位置更改
  • DOM 元素的尺寸更改
  • 插入或移除 DOM 元素
  • 触发布局的动画

特别是,紧接在偏移元素之前的 DOM 元素是最有可能导致“导致”布局偏移的元素。因此,在调查发生布局偏移的原因时,请考虑以下几点:

  • 上一个元素的位置或尺寸是否发生了变化?
  • 在偏移的元素之前,是否插入或移除了 DOM 元素?
  • 偏移元素的位置是否明确发生变化?

如果前面的元素没有导致布局偏移,请继续搜索,考虑其他前面和附近的元素。

此外,布局偏移的方向和距离也可以提供有关根本原因的提示。例如,较大的向下偏移通常表示插入了 DOM 元素,而 1 px 或 2 px 的布局偏移通常表示应用了有冲突的 CSS 样式,或加载和应用某种网页字体。

显示由字体交换导致的布局偏移的示意图
在此示例中,字体交换会使页面元素向上移动 5 个像素。

以下是最常导致布局偏移事件的一些特定行为:

元素的位置变化(不是由另一个元素移动导致的)

此类更改通常是由以下原因造成的:

  • 延迟加载或覆盖之前声明样式的样式表。
  • 动画和过渡效果。

元素尺寸更改

此类更改通常是由以下原因造成的:

  • 延迟加载或覆盖之前声明样式的样式表。
  • 没有 widthheight 属性的图片和 iframe,在其“槽位”呈现后加载。
  • 没有 widthheight 属性的文本块,这些属性可在文本渲染后交换字体。

插入或移除 DOM 元素

这通常是由于以下原因造成的:

  • 插入广告和其他第三方嵌入内容。
  • 插入横幅广告、提醒和模态窗口。
  • 无限滚动和其他用户体验模式,用于在现有内容上方加载其他内容。

触发布局的动画

某些动画效果可能会触发布局。一个常见的示例是,通过递增 topleft 等属性(而不是使用 CSS 的 transform 属性)为 DOM 元素添加“动画效果”。如需了解详情,请参阅如何创建高性能 CSS 动画

重现布局偏移

您无法修复无法重现的布局偏移问题。要更好地了解您网站的布局稳定性,最简单的方法是花 5-10 分钟的时间与网站进行互动,最终目标是触发布局偏移。在执行此操作时,请让控制台保持打开状态,并使用 Layout Instability API 报告布局偏移。

如果难以找到布局偏移,可以考虑使用不同的设备和连接速度重复本练习。特别是,使用较慢的连接速度可以更轻松地识别布局偏移。此外,您还可以使用 debugger 语句更轻松地逐步执行布局偏移。

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      debugger;
      console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift', buffered: true});

最后,对于开发过程中无法重现的布局问题,不妨考虑将 Layout Instability API 与您选择的前端日志记录工具结合使用,以收集有关这些问题的更多信息。请查看关于如何跟踪网页上最大的偏移元素的代码