优化输入延迟

了解什么是输入延迟,并学习减少输入延迟的技巧,提高互动速度。

网络上的互动非常复杂,浏览器会进行各种活动来推动互动。不过,它们的共同点是,在事件回调开始运行之前都会发生一些输入延迟。在本指南中,您将了解什么是输入延迟,以及如何最大限度地缩短输入延迟,以便加快您网站的互动速度。

什么是输入延迟?

输入延迟是指从用户首次与页面互动(例如点按屏幕、使用鼠标或按下按键)开始,一直到互动事件回调开始运行的时间段。每次互动开始时都会有一定的输入延迟。

简化的输入延迟可视化。左侧是鼠标光标的线条图,后面有一个星串,表示互动开始。右侧是齿轮的线条图,表示互动的事件处理脚本何时开始运行。中间的空格被标注为带有大括号的输入延迟。
输入延迟背后的机制。操作系统收到输入后,必须在交互开始前将输入传递给浏览器。这需要一定的时间,并且可以通过现有主线程工作增加。

输入延迟的某些部分是不可避免的:操作系统总是需要一些时间才能识别输入事件并将其传递给浏览器。不过,这部分输入延迟通常并不明显,而网页上发生的其他情况可能会导致输入延迟过长,导致出现问题。

如何看待输入延迟

一般来说,您应尽可能缩短互动的每个部分,这样您的网站才最有可能满足“互动到下一次绘制”(Interaction to Next Paint) (INP) 指标的“良好”指标阈值(不考虑用户的设备)。检查输入延迟只是满足该阈值的一个环节。

因此,您需要尽可能缩短输入延迟时间,以满足 INP 的“良好”需求阈值。不过请注意,您不能指望完全消除输入延迟。只要您避免在用户尝试与您的页面互动时执行过多的主线程工作,您的输入延迟就会足够低,以避免出现问题。

如何最大限度地缩短输入延迟

如前所述,某些输入延迟是不可避免的,但另一方面,某些输入延迟是可以避免的。遇到输入延迟问题时,请考虑以下事项。

避免触发过多主线程工作的周期性计时器

JavaScript 中有两个常用的计时器函数会导致输入延迟:setTimeoutsetInterval。两者之间的区别在于,setTimeout 会安排一个回调在指定时间后运行。另一方面,setInterval 会安排一个回调,每 n 毫秒永久运行一次,或者使用 clearInterval 停止计时器。

setTimeout 本身没有问题,实际上,它对避免耗时较长的任务很有帮助。不过,这取决于发生超时的时间,以及用户在超时回调运行时是否会尝试与网页交互。

此外,setTimeout 可以在循环中运行或以递归方式运行,其作用更类似于 setInterval,但最好等到前一个迭代完成时再安排下一次迭代。虽然这意味着每次调用 setTimeout 时循环都会让给主线程,但您应注意确保其回调最终不会执行过多的工作。

setInterval 会按一定间隔运行回调,因此更有可能妨碍互动。这是因为,与 setTimeout 调用的单个实例(可能会妨碍用户互动的一次性回调)不同,setInterval 的周期性特性使其更有可能妨碍互动,从而增加互动的输入延迟时间。

Chrome 开发者工具中性能分析器的屏幕截图,展示输入延迟。由计时器函数触发的任务会在用户发起点击互动之前发生。不过,计时器延长了输入延迟,导致互动事件回调的运行时间晚于正常时间。
由之前的 setInterval 调用注册的计时器,导致输入延迟,如 Chrome 开发者工具的性能面板所示。增加的输入延迟会导致互动事件回调的运行时间晚于本应运行的时间。

如果计时器出现在第一方代码中,那么您可以进行控制。请评估是否需要,或尽力减少其中的工作。不过,第三方脚本中的计时器则不然。您通常无法控制第三方脚本的用途,修复第三方代码的性能问题通常需要与利益相关方合作,以确定是否有必要使用指定的第三方脚本,如果需要,请与第三方脚本供应商联系,确定可采取哪些措施来解决这些脚本可能导致的网站性能问题。

避免耗时较长的任务

减少较长的输入延迟的一种方法是避免执行耗时较长的任务。如果在交互过程中有过多的主线程工作阻塞主线程,那么长任务还没来得及完成,就会增加线程的输入延迟。

直观呈现任务延长输入延迟时长的时间。首先,交互会在单个长任务运行后立即发生,从而导致明显的输入延迟,导致事件回调的运行时间远远晚于应有的运行时间。基本上,交互发生的时间大致相同,但通过让出,长任务被分解成了几个较小的任务,使交互的事件回调运行得更快。
当任务时间过长且浏览器无法足够快速地响应互动时,与将较长的任务拆分为较小的任务相比,互动情况的可视化。

除了尽量减少在任务中完成的工作量(您应该始终尽量减少在主线程上完成的工作)之外,您还可以通过分解冗长的任务来提高对用户输入的响应速度。

注意互动重叠

如果您的互动存在重叠,在优化 INP 时尤为具有挑战性。“互动重叠”意味着,您与一个元素进行了互动后,在初始互动还来得及渲染下一帧之前与网页进行了另一次互动。

描述任务何时会重叠以产生较长输入延迟的描述。在该描述中,点击互动与 keydown 互动重叠,增加了 keydown 互动的输入延迟时间。
Chrome 开发者工具的性能分析器中两次并发交互的可视化效果。初始点击互动中的渲染工作会导致后续键盘互动出现输入延迟。

互动重叠的来源可能非常简单,比如用户在短时间内进行多次互动。当用户在表单字段中输入内容时,就可能会出现上述情况,因为在很短的时间内可能会发生很多键盘互动。如果处理关键事件所需的开销特别大(例如在常见的自动填充字段(即向后端发出网络请求)中),您有以下两种选择:

  • 请考虑使输入去抖动,以限制事件回调在给定时间段内执行的次数。
  • 使用 AbortController 取消传出的 fetch 请求,使主线程在处理 fetch 回调时不会变得拥挤。注意:AbortController 实例的 signal 属性也可用于中止事件

重叠交互导致输入延迟增加的另一个原因可能是动画开销很大。特别是,JavaScript 中的动画可能会触发许多 requestAnimationFrame 调用,这可能会妨碍用户互动。为了解决这个问题,请尽可能使用 CSS 动画,以避免将可能开销高昂的动画帧排入队列,但这样做时,请务必避免使用非合成的动画,以使动画主要在 GPU 和合成器线程上运行,而不是在主线程上运行。

总结

虽然输入延迟并不一定代表互动的大部分运行时间,但请务必了解,互动的每一部分都会占用您可以缩短的时间。如果您观察到输入延迟较长,有机会缩短此延迟时间。避免周期性的计时器回调、中断耗时较长的任务,以及注意潜在的互动重叠,都有助于缩短输入延迟,从而提高网站用户的互动速度。

来自 Unsplash 的主打图片,Erik Mclean.