优化输入延迟

了解什么是输入延迟,并学习相关技巧来缩短延迟时间,从而提高互动速度。

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

什么是输入延迟?

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

输入延迟的简化可视化图表。左侧是鼠标光标的线稿,后面是星爆,表示互动开始。右侧是齿轮的线框图,表示互动事件处理程序何时开始运行。中间的空白区域用大括号表示为输入延迟。
输入延迟背后的机制。当操作系统收到输入时,必须在互动开始之前将其传递给浏览器。这需要一定的时间,并且可能会因现有的主线程工作而增加。

输入延迟的一部分是不可避免的:操作系统始终需要花费一定的时间来识别输入事件并将其传递给浏览器。不过,输入延迟的这一部分通常甚至不会被注意到,而且网页本身发生的其他事情可能会导致输入延迟时间过长,从而引发问题。

如何看待输入延迟

一般来说,您应尽可能缩短互动的每个部分,以便您的网站有更大的机会达到 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 提供。