提高文本输入的响应能力

2021-08-09 01:50:28

对我来说,网络上最严重的性能问题之一是在文本输入中打字很慢。我是一个相当快的打字员,所以即使在 <textarea> 或 <input> 中有一点延迟,我都能感觉到它让我慢下来,它让我发疯。我觉得这个问题特别烦人,因为它通常可以通过一些简单的技巧来解决。聊天应用程序或社交媒体应用程序的输入速度没有理由很慢,除非网络开发人员经常采取天真的方法,这就是延迟的来源。要了解输入延迟的来源,让我们举一个具体的例子。想象一个类似 Twitter 的 UI,带有一个文本字段和一个“剩余字符”计数。随着您键入,数字逐渐减少到零。这是一个活生生的例子。如果您没有注意到输入延迟,那么真的在键盘上混搭:天真的方法的问题在于,相对于用户从“剩余字符”显示中获得的好处而言,它通常最终做了太多的工作。在最坏的情况下,更改全局状态可能会导致整个 UI 重新渲染(例如,在优化不佳的 React 应用程序中),这意味着当用户键入时,每次按键都会导致完整的全局重新渲染。另外,因为我们直接监听输入事件,所以实际按键和出现在 <textarea> 中的字符之间会有延迟。因为 DOM 是单线程的,并且因为我们在主线程上进行阻塞工作,所以浏览器在工作完成之前无法呈现新输入。这可能会导致明显的打字延迟,从而导致用户感到沮丧。我对此类问题的首选解决方案是使用 requestIdleCallback 等待 UI 线程空闲,然后再运行阻塞代码。例如,这样的事情:

我们不会用任何昂贵的东西直接阻塞输入事件,所以在输入一个字符和看到该字符出现在 <textarea> 之间不应该有延迟。我们不会为每个按键更新 UI。 requestIdleCallback 将在用户在输入字符之间暂停时批量更新 UI。这是明智的,因为用户可能并不关心“剩余字符”计数是否为每个按键更新——他们的注意力集中在文本字段上,而不是剩余字符上。在较慢的机器上, requestIdleCallback 自然会比在较快的机器上执行更少的每次按键批处理。因此,使用更快设备的用户将受益于更快更新的 UI,但两个用户都不会遇到糟糕的输入响应。这是优化版本的一个活生生的例子。随意在键盘上混搭:你不应该看到(很多)延迟!过去,您可能使用过诸如 debounce 之类的方法来解决此问题。但是我喜欢 requestIdleCallback 因为上面第三点:它自然适应用户设备的特性,而不是强迫我们选择硬编码延迟。注意:在 web worker 中运行你的状态逻辑也是避免这个问题的一种方法。但是绝大多数 Web 应用程序都不是以这种方式构建的,因此我发现 requestIdleCallback 作为附加解决方案更好。公平地说,这种技术并非万无一失。一些 UI 确实需要立即响应每个按键:例如,禁止某些字符或随着 <textarea> 的增长调整其大小。 (不过,在这些情况下,我会使用 requestAnimationFrame 进行节流。)此外,如果某些 UI 正在执行的工作足够大,即使在批处理时也可以察觉,它们可能仍会滞后。 (在上面的实时示例中,我设置了 70 毫秒的人为延迟,您仍然可以“感觉到”优化版本。)但在大多数情况下,使用 requestIdleCallback 足以摆脱任何主要的响应问题。

如果您想在您自己的网站上对此进行测试,我建议您将 Chrome DevTools 置于 6 倍 CPU 速度下,然后尽可能快地敲击键盘。在没有 JavaScript 处理程序的普通 <textarea> 或 <input> 上,您不会看到任何延迟。而如果您自己的网站感觉缓慢,那么也许是时候优化您的文本输入了!