- 原文地址:Inside look at modern web browser (part 4)
- 原文做者:Mariko Kosaka
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:ThomasWhyne
- 校对者:llp0574 CoolRice
内部揭秘系列博客对现代浏览器如何处理代码、显示页面展开探讨。该系列博客共四篇,这是最后一篇。在上篇博客里,咱们了解了 渲染进程与合成器。这里咱们将一窥当用户输入行为发生时,合成器如何继续保障交互流畅。前端
听到“输入事件”这个字眼,你脑海里闪现的恐怕只是输入文本或点击鼠标。但在浏览器眼中,输入意味着一切用户行为。不单滚动鼠标滑轮是输入事件,触摸屏幕、滑动鼠标一样也是用户输入事件。android
诸如触摸屏幕之类用户手势产生时,浏览器进程会率先将其捕获。然而浏览器进程所掌握的信息仅限于行为发生的区域,由于标签页里的内容都由渲染进程负责处理,因此浏览器进程会将事件类型(如 touchstart
)及其坐标发送给渲染进程。渲染进程会寻至事件目标,运行其事件监听器,妥善地处理事件。ios
图 1:输入事件由浏览器进程发往渲染进程git
图 2:悬于页面图层的视图窗口github
在上篇文章里,咱们探讨了合成器如何经过合成栅格化图层,实现流畅的页面滚动。若是页面上没有添加任何事件监听,合成器线程会建立独立于主线程的新合成帧。但要是页面上添加了事件监听呢?合成器线程又是如何得知事件是否须要处理的?web
由于运行 JavaScript 脚本是主线程的工做,因此页面合成后,合成进程会将页面里添加了事件监听的区域标记为“非当即可滚动区”。有了这个信息,若是输入事件发生在这一区域,合成进程能够肯定应将其发往主线程处理。如输入事件发生在这一区域以外,合成进程则肯定无需等待主线程,可继续合成新帧。chrome
图 3:非当即可滚动区输入描述示意图后端
web 开发中经常使用的事件处理模式是事件代理。由于事件会冒泡,因此你能够在最顶层的元素中添加一个事件处理器,用来代理事件目标产生的任务。下面这样的代码,你可能见过,或许也写过。浏览器
document.body.addEventListener('touchstart',
event => {
if (event.target === area) {
event.preventDefault();
}
});
复制代码
这样只需添加一个事件监听器,便可监听全部元素,的确十分省事。然而,若是站在浏览器的角度去考量,这等于把整个页面都标记成了“非当即可滚动区”,意味着即使你设计的应用本没必要理会页面上一些区域的输入行为,合成线程也必须在每次输入事件产生后与主线程通讯并等待返回。如此则得不偿失,使本来能保障页面滚动流畅的合成器没了用武之地。bash
图 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(); // 阻止默认的滚动行为
/*
* 这里设置程序执行任务
*/
}
}, {passive:: true});
复制代码
或者,你也能够应用 touch-action
这类 CSS 规则,彻底地将事件处理器屏蔽掉。
#area {
touch-action: pan-x;
}
复制代码
图 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:左为流畅的触摸手势路径、右为事件合并后的有限路径
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// 使用 x、y 坐标画线
}
});
复制代码
本系列文章里,咱们探讨了不少关于 web 浏览器内部的工做原理。若是以前你历来没想过:为何 Devtools 推荐在事件处理器上添加 {passive:true}
选项、为何有时须在 script 标签里添加 async
属性?那么我但愿这一系列文章能帮助你了解,为何传递这些信息给浏览器能让其提供更为迅捷流畅的 web 体验。
若是你想构建出对浏览器更为友好的代码,却一直毫无头绪,那么不妨先从使用 Lighthouse 开始。Lighthouse 是个能够帮助你审查网站工具,而且能提供页面性能报告。性能报告会告诉你什么地方处理得当,什么地方有待提高。浏览审查列表也能让你了解浏览器着力关注的重点所在。
对于不一样的站点,桎梏其性能之处可能不尽相同,因此专门为你本身的站点定制化一套性能评测方案,并择优选取技术应用,是重中之重。Chrome 的 Devtools 团队就 如何测试你的站点性能 撰有相关教程可供参阅。
若是你想进一步采用更多方案,Feature Policy 是一个新的 web 平台,可在开发时为你保驾护航。开启 feature policy 能够限制应用行为,并使你远离诸多技术弊端。举个例子,若是你想确保应用不会阻塞解析,那么能够采用同步脚本方案运行应用。开启 sync-script:'none'
后,致使解析阻塞的 JavaScript 脚本会被阻止运行。这就确保了你的代码不会阻塞解析,浏览器也无须考虑暂停运行解析器。
刚踏上开发之路时,我几乎只关注怎样去写代码、怎样提高本身的生产效率。诚然,这些事情很重要,但与此同时咱们也应当思考浏览器会怎么去处理咱们书写的代码。现代浏览器一直致力探索如何提供更好的用户体验。书写对浏览器友好的代码,反过来也能提供友好的用户体验。路漫漫其修远兮,但愿咱们能携手共进,构建出对浏览器更为友好的代码。
在此笔者诚挚感谢 Alex Russell、Paul Irish、Meggin Kearney、Eric Bidelman、Mathias Bynenes、Addy Osmani、Kinuko Yasuda、Nasko Oskov 和 Charlie Reis 等人对本系列文章初稿的校对。
你喜欢这一系列的文章吗?如对以后文章有任何意见或建议,欢迎在下面评论区或是推特 @kosamari 里留下您的宝贵意见。
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。