深入了解现代网络浏览器(第 4 部分)

Mariko Kosaka

合成器有输入源

本文是“Chrome 浏览器”系列博文(共 4 部分)中的最后一部分;调查它如何处理 我们的代码来显示网站在上一篇博文中,我们介绍了渲染过程并了解了合成器。在本文中,我们将 我们看看在用户输入内容时,合成器如何实现流畅的交互。

从浏览器的角度输入事件

当你听到“输入事件”时您可能只会想到在文本框中进行输入或点击鼠标, 从浏览器的角度来看,输入表示用户执行的任何手势。鼠标滚轮滚动是一项输入 事件,触摸或鼠标悬停也是一个输入事件。

当在屏幕上发生触摸等用户手势时,浏览器进程就是接收 手势。但是,浏览器进程只能知道该手势的发生位置, 标签页内的内容由渲染程序进程处理。因此,浏览器进程将事件 类型(如 touchstart)及其坐标传递给渲染程序进程。渲染程序进程负责处理 通过查找事件目标并运行附加的事件监听器来相应地触发该事件。

输入事件
图 1:输入事件通过浏览器进程路由到渲染器进程

合成器接收输入事件

<ph type="x-smartling-placeholder">
</ph> 图 2:视口悬停在页面图层上

在上一篇博文中,我们了解了合成器如何通过 光栅化图层。如果页面未附加任何输入事件监听器,则合成器线程 创建完全独立于主线程的新复合框架。但如果某些活动 监听器附加到了网页?合成器线程如何确定事件是否需要 处理方式?

了解不可快速滚动的区域

由于运行 JavaScript 是主线程的工作,因此在合成页面时,合成器线程 将附加了事件处理脚本的网页区域标记为“非快速滚动区域”。修改者 获得这些信息后,合成器线程可以确保将输入事件发送到主线程 如果事件发生在该地区如果输入事件来自该区域之外,那么 合成器线程会开始合成新帧,而不等待主线程。

受限的不可快速滚动区域
图 3:描述到不可快速滚动区域的输入的示意图

编写事件处理脚本时要注意

Web 开发中的一种常见的事件处理模式是事件委托。由于事件气泡 可以在最顶端的元素上附加一个事件处理脚本,并根据事件目标委托任务。您 可能曾查看或编写过如下代码。

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

由于您只需为所有元素编写一个事件处理脚本,因此该事件的工效学设计是 很有吸引力但是,如果您从浏览器的角度来看 视图,现在整个页面都会被标记为不可快速滚动的区域。这意味着,即使您的 应用并不在意来自页面某些部分的输入,因此合成器线程必须 与主线程进行通信,并在每次有输入事件进入时等待主线程。因此, 合成器的流畅滚动功能失效。

整页非快速滚动区域
图 4:覆盖整个页面的不可快速滚动区域的所描述输入的示意图

为了减少这种情况的发生,您可以在事件中传递 passive: true 选项 监听器。这将提示浏览器您仍想监听主线程中的事件, 但合成器也可以继续合成新帧

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

检查活动是否可取消

页面滚动
图 5:部分页面固定为水平滚动的网页

假设您在网页上有一个框,您希望将滚动方向限制为仅水平滚动。

在指针事件中使用 passive: true 选项意味着页面可以顺畅滚动,但 自动滚动可能在您所需的时间preventDefault开始,以限制 滚动方向。您可以使用 event.cancelable 方法对此进行检查。

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

或者,您也可以使用 CSS 规则(如 touch-action)来完全消除事件处理脚本。

#area {
  touch-action: pan-x;
}

查找事件目标

点击测试
图 6:主线程查看绘制记录,询问在 x.y 点上绘制的内容

当合成器线程向主线程发送输入事件时,首先要运行的是 以便找到事件目标点击测试使用绘制记录在渲染中生成的数据 过程找出事件发生的点坐标下面的内容。

尽可能减少向主线程分派事件的次数

在上一篇博文中,我们讨论了典型的显示屏如何每秒刷新 60 次 我们需要如何跟上流畅动画的节奏。对于输入,典型的触摸屏 设备每秒传递触摸事件 60-120 次,一般鼠标每秒传递事件 100 次 。输入事件的保真度高于屏幕可以刷新的保真度。

如果 touchmove 等连续事件每秒向主线程发送 120 次,则 与网页访问速度相比, 屏幕可以刷新。

未过滤的事件
图 7:在帧时间轴中填充事件导致页面卡顿

为尽可能减少对主线程的过多调用,Chrome 会合并连续事件(例如 wheelmousewheelmousemovepointermovetouchmove ),并延迟调度至 就在下一个requestAnimationFrame之前。

合并事件
图 8:与之前相同的时间轴,但事件发生了合并和延迟

任何离散事件,例如 keydownkeyupmouseupmousedowntouchstarttouchend 所有资源

使用 getCoalescedEvents 获取帧内事件

对于大多数 Web 应用,合并事件应该足以提供良好的用户体验。 不过,如果您要构建一些内容,例如绘制应用, touchmove 坐标,则可能会丢失中间的坐标,以绘制平滑线。在这种情况下 则可以在指针事件中使用 getCoalescedEvents 方法获取有关这些事件的信息 合并事件

getCoalescedEvents
图 9:左侧是平滑的触摸手势路径,右侧是合并的有限路径
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

后续步骤

在本系列中,我们介绍了网络浏览器的内部工作原理。如果你从未想过 开发者工具建议在事件处理脚本上添加 {passive: true} 或编写 async 的原因 属性,我希望本系列视频能帮助您 了解浏览器为何需要使用这些属性 从而提供更快速、更顺畅的网络体验。

使用 Lighthouse

如果您希望代码对浏览器有益,但又不知道从何处入手, Lighthouse 是一款可对任何网站进行审核的工具,可为您提供 报告哪些方面做得不错,哪些方面需要改进。仔细阅读审核列表 也可以让您了解浏览器关注哪些内容。

了解如何衡量性能

性能调整可能因网站而异,因此衡量广告效果至关重要 并确定哪些内容最适合您的网站。Chrome 开发者工具团队关于 如何衡量网站性能

向您的网站添加功能政策

如果想要更进一步,功能政策是一项新功能 Web 平台功能,可作为您构建项目时的保障。正在开启 功能政策可保证应用的某些行为,并防止您出错。 例如,如果您想确保应用永远不会阻止解析,则可以在 同步脚本政策。sync-script: 'none'已启用后,会阻止解析器的 JavaScript 无法执行。这可以防止您的任何代码阻止解析器,而 因此浏览器无需担心暂停解析器。

小结

谢谢

刚开始构建网站时,我几乎只关心如何编写代码,以及 有助于我提高工作效率这些都很重要,但我们还应该考虑 浏览器接受我们编写的代码。现代浏览器一直并且仍在继续投资 为用户提供更好的网络体验。我们通过整理代码来为浏览器美化 从而改善用户体验希望您和我一起为浏览器友善!

非常感谢审核本系列早期草稿的所有人,包括(但不限于) Alex RussellPaul IrelandMeggin KearneyEric BidelmanMathias BynensAddy OsmaniKinuko YasudaNasko Oskov、 以及 Charlie Reis。

您喜欢这个系列吗?如果您对以后的帖子有任何疑问或建议, 期待在下面的评论部分或 @kosamari 提供宝贵意见 。