深入了解现代 web 浏览器(第四部分)
本文为译文,内容尽可能保持和原文的一致性,如果翻译有误,欢迎批评指正。
原文作者:Mariko Kosaka
原文标题:Inside look at modern web browser (part 4)
原文链接:https://developers.google.com/web/updates/2018/09/inside-browser-part4
输入进入合成器
这是关于 Chrome 内部的 4 部分博客系列的最后一部分;调查它如何处理我们的代码以显示网站。在上一篇文章中,我们查看了渲染过程并了解了合成器。在这篇文章中,我们将看看合成器如何在用户输入时实现流畅地交互。
从浏览器的角度输入事件
当你听到“输入事件”时,你可能只会想到在文本框中键入或鼠标单击,但从浏览器的角度来看,输入意味着来自用户的任何手势。鼠标滚轮滚动是一个输入事件,触摸或鼠标悬停也是一个输入事件。
当用户在屏幕上及逆行触摸等手势时,浏览器进程首先接收该手势。但是,浏览器进程只知道该手势发生的位置,因为选项卡内的内容由渲染器进程处理。因此浏览器进程将事件类型(如 touchstart)及其坐标发送到渲染器进程。渲染器进程通过查找事件目标并运行附加的事件侦听器来适当地处理事件。
图 1:通过浏览器进程路由到渲染器进程的输入事件
合成器接收输入事件
在上一篇文章中,我们研究了合成器如何通过合成光栅化图层来平滑的处理滚动。如果没有输入事件监听器附加到页面,合成器线程可以创建一个完全独立于主线程的新复合框架。但是如果某些事件侦听器附加到页面上呢?合成器线程如何确定是否需要处理事件?
https://player.bilibili.com/player.html?bvid=bv1tA411c7ZP
图 2:视口悬停在页面图层上
理解非快速滚动区域
由于运行 JavaScript 是主线程的工作,因此在合成页面时,合成器线程会将页面中附加有事件处理程序的区域标记为“非快速可滚动区域”。通过获得这些信息,合成器线程可以确保在该区域发生事件时将输入事件发送到主线程。如果输入事件来自该区域之外,则合成器线程继续合成新帧,而无需等待主线程。
图 3:非快速滚动区域的描述输入图
编写事件处理程序时要注意
Web 开发中常见的事件处理模式是事件委托。由于事件冒泡,你可以在最顶层元素附加一个事件处理程序,并根据事件目标委派任务。你可能已经看到或编写了如下代码。
由于你只需要为所有元素编写一个事件处理程序,因此这种事件委托模式的人体工程学很有吸引力。但是,如果你从浏览器的角度来看这段代码,整个页面都被标记为非快速滚动区域。这意味着即使你的应用程序不关心来自页面某些部分的输入,合成器线程也必须和主线程通信并在每次输入事件进入时等待它,因此,合成器的平滑滚动能力被打败了。
图 4:覆盖整个页面的非快速可滚动区域的描述输入图
为了减轻这种情况的发生,你可以在事件侦听器中传递 passive: true 选项。这向浏览器暗示你仍然希望在主线程中侦听事件,但合成器也可以继续合成新帧。
检查事件是否可以取消
想象一下,你希望将滚动方向限制为仅水平滚动的页面中有一个框。
在指针事件中使用 passive: true 选项意味着页面滚动可以平滑,但垂直滚动可能在你想要阻止默认以限制滚动方向时已经开始。你可以使用 event.cancelable 方法对此进行检查。
图 5:部分页面固定为水平滚动的网页
或者,你可以使用像 touch-action 这样的 CSS 规则来完全消除事件处理程序。
寻找事件目标
当合成器线程向主线程发送输入事件时,首先要运行的是命中测试以找到事件目标。命中测试使用渲染过程中生成的绘制记录数据来找出发生事件的点坐标下方的内容。
图 6:主线程查看绘制记录,询问在 x,y 点上绘制了什么
最小化事件调度到主线程
在上一篇文章中,我们讨论了我们的典型显示器如何每秒刷新屏幕 60 次,以及我们需要如何跟上节奏已获得流畅地动画。对于输入,典型的触摸屏设备每秒传递 60-120 次触摸事件,典型的鼠标每秒传递 100 次事件。输入事件的保真度高于我们的屏幕可以刷新的保真度。
如何像 touchmove 这样的连续事件每秒被发送到主线程 120 次,那么与屏幕刷新的速度相比,它可能会触发过多的命中测试和 JavaScript 执行。
图 7:事件淹没帧时间线导致页面卡顿
为了尽量减少对主线的过多调用,Chrome 会合并连续事件(例如 wheel, mousewheel, mousemove, pointermove, touchmove)并将调度延迟到下一个 requestAnimationFrame 之前。
图 8:与之前相同的时间线,但事件被合并和延迟
任何离散事件,如 keydown、keyup、mouseup、mousedown、touchstart 和 touchend 都会立即分派。
使用 getCoalescedEvents 获取帧内事件
对于大多数 Web 应用程序,合并事件应该足以提供良好的用户体验。但是如果你正在构建诸如绘图应用程序和基于 touchmove 坐标放置路径之类的东西,你可能会丢失中间坐标以绘制平滑线。在这种情况下,你可以在指针事件中使用 getCoalescedEvents 方法来获取有关这些合并事件的信息。
图 9:左侧是平滑的触摸手势路径,右侧是合并的有限路径
下一步
在本系列中,我们介绍了 Web 浏览器的内部工作原理。如果你从未想过为什么 DevTools 建议在你的事件处理程序中添加 {passive: true} 或者为什么你可能会在你的脚本标签中编写 async 属性,我希望这个系列能阐明为什么浏览器需要这些信息来提供更快、更流畅的信息网络体验。
使用 Lighthouse
如果你想让你的代码对浏览器友好,但不知道从哪里开始,Lighthouse 是一个工具,可以运行任何网站的审计,并为你提供一份报告,说明哪些事情做得对,哪些地方需要改进。通读审核列表还可以让你了解浏览器关心的事情类型。
了解如何衡量绩效
不同站点的性能调整可能会有所不同,因此衡量站点的性能并确定最适合你站点的方法至关重要。Chrome DevTools 团队几乎没有关于如何衡量网站性能的教程。
将功能策略添加到你的站点
如果你想采取额外的措施,Feature Policy 是一项新的 Web 平台功能,可以在你构建项目时为你提供护栏。启用功能策略可保证你的应用程序的某些行为并防止你犯错误。例如,如果你想确保你的应用程序永远不会阻止解析,你可以在同步脚本策略上运行你的应用程序。当启用 sync-script: 'none' 时,将阻止解析器阻塞 JavaScript 的执行。这可以防止你的任何代码阻塞解析器,并且浏览器无需担心暂停解析器。
总结
当我开始构建网站是,我几乎只关心如何编写代码以及如何提高工作效率。这些东西很重要,但我们也应该考虑浏览器如何处理我们编写的代码。现代浏览器一直并将继续投资于为用户提供更好的网络体验的方法。通过组织我们的代码对浏览器友好,反过来又可以改善你的用户体验,我希望你和我一起努力对浏览器友好!
非常感谢审阅本系列早期草稿的所有人,包括(但不限于):Alex Russell、Paul Irish、Meggin Kearney、Eric Bidelman、Mathias Bynens、Addy Osmani、Kinuko Yasuda、Nasko Oskov 和 Charlie Reis。
你喜欢这个系列吗? 如果您对以后的帖子有任何问题或建议,我很乐意在下面的评论部分或 Twitter 上的 @kosamari 收到您的来信。
版权声明: 本文为 InfoQ 作者【GKNick】的原创文章。
原文链接:【http://xie.infoq.cn/article/0d700b848bf0bac6b734efd00】。文章转载请联系作者。
评论