优化输入延迟

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

杰里米·瓦格纳
Jeremy Wagner

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

什么是输入延迟?

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

输入延迟的简化可视化效果。左侧是一张线条画,它是鼠标光标的线形图,上面有星形图案,表示互动已经开始了。右侧是齿轮的线条画,表示互动的事件处理脚本何时开始运行。中间的空格表示为带有大括号的输入延迟。
输入延迟背后的机制。操作系统收到输入内容后,必须先将其传递到浏览器,然后才能开始互动。这需要一定的时间,并且可能会因现有的主线程工作而增加。

部分输入延迟是不可避免的:操作系统始终需要一些时间来识别输入事件并将其传递给浏览器。不过,这部分输入延迟通常并不明显,网页本身上还会发生其他一些情况,导致输入延迟足够长,进而导致问题。

如何看待输入延迟

一般来说,您需要尽量缩短互动的每个部分,这样无论用户使用的是何种设备,您的网站都有可能达到“Interaction to Next Paint (INP)”指标的“良好”阈值。检查输入延迟只是达到该阈值的一部分。

您可能想要查看 First Input Delay (FID) 阈值来确定允许输入延迟的情况,但 FID 的“良好”阈值不超过 100 毫秒。如果您超过此阈值,则系统会仅将一半预算分配给 INP 输入延迟。如果您考虑到交互还需要时间来运行事件回调和浏览器绘制下一帧,则这种做法不可取。

要达到 INP 的“良好”阈值,您需要力争尽可能缩短输入延迟时间,但也不要指望将其完全消除,因为这是不可能的。只要您避免在用户尝试与您的页面交互时主线程执行过多操作,输入延迟时间就应足够短,以免出现问题。

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

如前所述,有些输入延迟是不可避免的,但从另一方面来说,有些输入延迟是可以避免的。如果您遇到长时间的输入延迟问题,请考虑以下事项。

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

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

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