优化 First Input Delay

如何更快地响应用户互动。

我点击了,但没有任何反应!为什么我无法与此网页互动?😝?

First Contentful Paint (FCP) 和 Largest Contentful Paint (LCP) 都是衡量内容在网页上进行视觉渲染(绘制)所需的时间。绘制时间虽然非常重要,但并不会捕获“加载响应速度”(即网页对用户互动的响应速度)。

First Input Delay (FID) 是一项核心网页指标指标,用于捕获用户对网站的互动性和响应速度的第一印象。该指标衡量的是从用户首次与网页互动到浏览器实际能够响应此次互动之间的用时。FID 是一种现场指标,无法在实验室环境中模拟。为了衡量响应延迟时间,需要开展真实的用户互动

良好保真值为 2.5 秒,差值大于 4.0 秒,介于两者之间的任何值都需要改进

为帮助在实验中预测 FID,我们建议启用 Total Blocking Time (TBT)。两者衡量的内容不同,但 TBT 的改善通常对应于 FID 的改善。

导致 FID 不佳的主要原因是大量 JavaScript 执行。优化 JavaScript 在网页上解析、编译和执行的方式可直接降低 FID。

JavaScript 密集执行

在主线程上执行 JavaScript 时,浏览器无法响应大多数用户输入。换言之,当主线程处于忙碌状态时,浏览器无法响应用户互动。要改善这种情况,请执行以下操作:

拆分长任务

如果您已尝试减少在单个网页上加载的 JavaScript 数量,将长时间运行的代码拆分为多个较小的异步任务会很有帮助。

长时间任务是指用户可能会发现界面无响应的 JavaScript 执行期。任何将主线程阻塞 50 毫秒或更长时间的代码段都可以被视作长任务。长时间运行的任务表明可能存在 JavaScript 膨胀(加载和执行的数量超出了用户当前可能需要的数量)。拆分长任务可以减少网站上的输入延迟。

Chrome 开发者工具中的长时间运行的任务
Chrome 开发者工具在“性能”面板中直观呈现长时间运行的任务

如果您采用代码拆分和拆分长任务等最佳实践,FID 应该会明显改善。虽然 TBT 不是现场指标,但有助于检查最终改进可交互时间 (TTI) 和 FID 的进度。

针对互动准备情况优化网页

在严重依赖 JavaScript 的 Web 应用中,FID 和 TBT 得分不佳有以下几种常见的原因:

第一方脚本执行可能会延迟互动准备情况

  • JavaScript 大小膨胀、执行时间过长以及数据块的效率低下,都可能会减慢网页响应用户输入的速度,并影响 FID、TBT 和 TTI。逐步加载代码和功能有助于分散这种工作并改进互动就绪性。
  • 服务器端渲染的应用可能看起来像是在屏幕上快速绘制了像素,但要注意大型脚本执行(例如通过重新水化来连接事件监听器)阻止用户互动。如果使用基于路由的代码拆分,此过程可能需要几百毫秒,有时甚至几秒钟。请考虑在服务器端迁移更多逻辑,或在构建时静态生成更多内容。

以下是为应用优化第一方脚本加载前后的 TBT 得分。通过将非必要组件的高成本脚本加载(和执行)脚本从关键路径中移出,用户可以更快地与页面互动。

优化第一方脚本后,Lighthouse 中的 TBT 得分有所改进。

数据提取可能影响互动准备的很多方面

  • 等待瀑布级提取(例如组件的 JavaScript 和数据提取)可能会影响互动延迟时间。旨在最大限度减少对级联数据提取的依赖。
  • 大型内嵌数据存储区可能会延长 HTML 解析时间,并同时影响绘制和互动指标。旨在最大限度减少需要在客户端进行后处理的数据量。

第三方脚本执行也会延迟互动延迟

  • 许多网站包含第三方跟踪代码和分析工具,它们可能会使网络保持忙碌状态,使主线程定期无响应,从而影响互动延迟时间。探索第三方代码的按需加载(例如,也许不要加载这些非首屏广告,直到它们滚动到更靠近视口的位置)。
  • 在某些情况下,第三方脚本在主线程上的优先级和带宽方面可能会抢占第一方脚本,同时也会延迟网页准备好互动所需的时间。尝试优先加载您认为能够为用户提供最大价值的内容。

使用 Web Worker

主线程阻塞是导致输入延迟的主要原因之一。Web 工作器让您可以在后台线程上运行 JavaScript。将非界面操作移至单独的工作器线程可以缩短主线程阻塞时间,从而改进 FID。

考虑使用以下库,以便更轻松地在您的网站上使用 Web Worker:

  • Comlink:一个辅助库,可抽象化 postMessage 并使其更易于使用
  • Workway:一种通用 Web Worker 导出器
  • Workerize:将模块移至 Web Worker

缩短 JavaScript 执行时间

限制网页上的 JavaScript 量可减少浏览器执行 JavaScript 代码所需的时间。这样可以加快浏览器开始响应任何用户互动的速度。

要减少您网页上执行的 JavaScript 数量,请执行以下操作:

  • 推迟加载未使用的 JavaScript
  • 尽量减少未使用的 polyfill

推迟加载未使用的 JavaScript

默认情况下,所有 JavaScript 都会阻止呈现。当浏览器遇到链接到外部 JavaScript 文件的脚本标记时,浏览器必须暂停正在执行的操作,然后下载、解析、编译和执行该 JavaScript。因此,您只应加载网页或响应用户输入所需的代码。

Chrome 开发者工具中的覆盖率标签页可以告知您网页上未使用的 JavaScript 数量。

“覆盖率”标签页。

如需减少未使用的 JavaScript,请执行以下操作:

  • 通过代码将软件包拆分为多个区块
  • 使用 asyncdefer 推迟所有非关键 JavaScript,包括第三方脚本

代码拆分是将单个大型 JavaScript 软件包拆分为可有条件地加载(也称为延迟加载)的较小区块的概念。大多数较新的浏览器都支持动态导入语法,该语法允许按需提取模块:

import('module.js').then((module) => {
  // Do something with the module.
});

在某些用户互动(例如更改路由或显示模态窗口)时动态导入 JavaScript,可确保系统仅在需要时提取初始网页加载未用到的代码。

除了一般的浏览器支持之外,动态导入语法还可用于许多不同的构建系统。

  • 如果您使用 webpackRollupParcel 作为模块打包器,可以利用它们的动态导入支持。
  • 客户端框架(如 ReactAngularVue)提供了抽象化功能,让您可以更轻松地在组件级延迟加载。

除了代码拆分之外,对于关键路径或首屏内容不需要的脚本,请始终使用 async 或 defer

<script defer src="…"></script>
<script async src="…"></script>

除非有明确的理由,否则所有第三方脚本都应该默认使用 deferasync 加载。

尽量减少未使用的 polyfill

如果您使用新型 JavaScript 语法编写代码并引用新型浏览器 API,则需要对其进行转译并包含 polyfill,才能使其在旧版浏览器中正常运行。

在网站中添加 polyfill 和转译代码的主要性能问题之一是,较新的浏览器在不需要时就无需下载。为了缩减应用的 JavaScript 大小,请尽可能减少未使用的 polyfill,并将其用于所需环境。

如需优化您网站上的 polyfill 使用情况,请执行以下操作:

  • 如果您使用 Babel 作为转译器,请使用 @babel/preset-env,以便仅包含您计划定位的浏览器所需的 polyfill。对于 Babel 7.9,请启用 bugfixes 选项以进一步减少任何不需要的 polyfill
  • 使用 module/nomodule 模式提供两个单独的软件包(@babel/preset-env 也通过 target.esmodules 支持此功能)

    <script type="module" src="modern.js"></script>
    <script nomodule src="legacy.js" defer></script>
    

    使用 Babel 编译的许多较新的 ECMAScript 功能在支持 JavaScript 模块的环境中已经受支持。因此,这样可以简化确保只有转译代码用于实际需要的浏览器的过程。

开发者工具

有许多工具可用于测量和调试 FID:

感谢 Philip Walton、Kayce Basques、Ilya Grigorik 和 Annie Sullivan 撰写的评价。