本文是根据Mariko Kosaka在谷歌开发者网站上的系列文章https://developer.chrome.com/blog/inside-browser-part4/翻译而来,共有四篇,该篇是第四篇。对于其中一些直译出来不太好理解的句子,笔者做了加工处理和提炼。

输入来到了合成器

在上篇文章中,我们了解了渲染过程和合成器;在这片文章中,我们将来了解下合成器是如何在用户输入到来时保持交互流畅的。

从浏览器的角度看输入事件

当你听到“输入事件”时,你可能只会想到在文本框中输入或是鼠标点击;但从浏览器的角度来看,来自用户的任何动作都是输入。鼠标滚轮滚动、触摸或者鼠标悬浮都是一个输入事件。

当用户在屏幕做出触摸等动作时,浏览器进程最先接收到该动作。但是浏览器进程只关注该动作发生的位置,因为tab页中的内容是由渲染进程处理的。浏览器进程把事件类型(比如touchstart)和其坐标发送给渲染进程;渲染进程会找到对应的事件目标并执行目标上绑定的事件监听器。

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

合成器接收输入事件

在上一篇文章中,我们知道了合成器是如何通过合成光栅化的图层来达到流畅地处理滚动的。如果页面上没有绑定事件监听,合成线程可以完全独立于主线程来创建合成帧。但如果页面上绑定有事件监听呢?毕竟事件监听的回调函数只能由主线程来执行。

理解非快速滚动区域

由于运行Javascript是主线程的工作,当页面被合成时,合成线程会将页面上绑定有事件监听的区域标记为“非快速滚动区域(Non-Fast Scrollable Region)”。拥有这些信息,合成线程能够确保当有事件发生在该区域时能将输入事件发送给主线程。如果输入事件是来自该区域之外的,则合成线程会继续合成新的帧而无需等待主线程执行事件处理函数。

非快速滚动区域的输入事件

注意你编写的事件处理函数

在web开发中一种很常见的处理模式是事件委派。由于事件冒泡机制的存在,你可以在最顶层的元素绑定事件处理函数然后根据事件目标来委派处理函数。你可能见过或者写过如下的代码:

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

由于你只需要为所有的元素只编写一个处理函数,从工程学上讲这种事件委派模式很有吸引力。但如果从浏览器的角度来看这段代码,现在整个页面都被标记为了“非快速滚动区域”。这意味着,即使你的应用并不关心页面上某些部分产生的输入事件,但只要发生了输入事件,合成线程还是不得不与主线程发生通信并等待。因此,合成线程的流畅性就会收到影响。

覆盖到了整个页面的非快速滚动区域

为了减轻这种情况的影响,你可以向事件处理函数中传入passive: true选项。这能提示浏览器你在事件处理函数中不会调用event.preventDefault(),就意味着你代码中不会通过event.preventDefault()语句阻止事件的默认行为(没有这个选项的话,则需要主线程执行完你的处理函数然后才能决定是否要阻止诸如滚动、失焦之类的事件默认行为,再告知合成线程要合成新的帧)。因此,在有passive: true的情况下,就可以让主线程在执行处理函数的同时,合成线程能继续合成下一帧。

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

寻找事件目标

当主线程收到合成线程发送的输入事件后,首先要做的就是执行命中测试来找到事件目标。命中测试利用渲染阶段生成的绘制记录来找出发生事件的点坐标下方的内容。

主线程根据绘制记录查询x.y点处绘制的内容

最小化事件调度到主线程

在上一篇文章中,我们讨论了典型的显示器是如何一秒钟刷新60次并保持动画流畅的节奏。对于输入,典型的触屏设备每秒会传递60-120次触屏事件,而典型的鼠标每秒会传递100次事件。输入事件的保真度高于我们屏幕刷新频率的保真度。

如果像touchmove这样的连续事件被每秒120次地发送到主线程,那么与屏幕的刷新速度相比,它会触发过多的命中测试和Javascript执行。

帧的时间轴上被大量事件淹没导致页面卡顿

为了尽量减少在主线程上的调用,Chrome会合并连续的事件(例如wheel, mousewheel, mousemove, pointermove, touchmove)并延迟调度直至下一次requestAnimationFrame之前。

与上一张图片相同的时间轴,但事件被合并和延迟了

任何的离散事件例如keydown,keyup,mouseup,mousedown,touchstarttouchend都会被立即调度。

使用getCoalescedEvents获取帧内事件

对于绝大部分的web应用,合并事件足以提供良好的用户体验。然而,如果你正在构建诸如绘画或者基于touchmove坐标的路径放置的应用,你可能会因丢失中间坐标而不能绘画出平滑的线段。这种情况下,你可以使用指针事件中的getCoalescedEvents方法来获取更多关于这些合并事件的信息。

左侧是平滑触摸手势的路径,右侧是受合并事件限制的路径

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.
}
})

下一步

在本系列中,我们介绍了Web浏览器的内部工作原理。如果您从未想过为什么DevTools建议在事件处理程序中添加{passive: true},或者为什么你可能要在脚本标签中写入async属性;我希望本系列文章能够阐明为什么浏览器需要这些信息来提供更快、更流畅的Web体验。

使用Lighthouse

如果你想让自己的代码对浏览器更友好但不知道从哪里开始,Lighthouse会是一个不错的工具。它可以对任何网站执行审计并提供一份报告,告知你哪些地方做的不错而哪些地方还需要改进。通过阅读审计列表,可以让你知道浏览器关心哪些指标。

了解如何衡量性能

对于不同站点性能调整会有所不同,因此如何衡量你的站点性能并确定最适合的方案是非常重要的。Chrome的DevTools团队有一些关于衡量站点性能的教程

总结

当我开始构建网站的时候,几乎只关心书写代码和哪些能帮助我提高效率的东西。这些事当然很重要,但我们也应该思考下浏览器是如何处理我们书写的代码的。现代浏览器为给用户提供更好的web体验而持续努力。书写对浏览器“友好”的代码,这反过来会改进你的用户体验。希望你能加入我们一起追求对浏览器更为友好的世界!

[译]深入了解现代web浏览器(四)的更多相关文章

  1. [译]36 Days of Web Testing(四)

    Day 19: UX  用户体验 Why ? 最近UX变得越来越火,用户提现往往会直接联想到易用性和设计. 在我看来,UX不仅仅是这两点.UX, User Experience ,对我而言,不单单是产 ...

  2. [C# 网络编程系列]专题四:自定义Web浏览器

    转自:http://www.cnblogs.com/zhili/archive/2012/08/24/WebBrowser.html 前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发 ...

  3. 转:【专题四】自定义Web浏览器

    前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发出请求的正是本专题要介绍的Web浏览器,本专题通过简单自定义一个Web浏览器来简单介绍浏览器的工作原理,以及帮助一些初学者揭开浏览器这 ...

  4. 专题四:自定义Web浏览器

    前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发出请求的正是本专题要介绍的Web浏览器,本专题通过简单自定义一个Web浏览器来简单介绍浏览器的工作原理,以及帮助一些初学者揭开浏览器这 ...

  5. 前端Web浏览器基于Flash如何实时播放监控视频画面(四)之使用videoJs‘拉流’

    本片文章只是起到抛砖引玉的作用,能从头到尾走通就行,并不做深入研究.为了让文章通俗易懂,尽量使用白话描述. 0x001: 下载videoJs 对于Video.js 5.x及更低版本,Flash技术(v ...

  6. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  7. java web 学习四(http协议)

    一.什么是HTTP协议 HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的 ...

  8. JavaScript权威指南--WEB浏览器中的javascript

    知识要点 1.客户端javascript window对象是所有客户端javascript特性和API的主要接入点.它表示web浏览器的一个窗口或窗体,并且可以用window表示来引用它.window ...

  9. 前端Web浏览器基于Flash如何实时播放监控视频画面(前言)之流程介绍

    [关键字:前端浏览器如何播放RTSP流画面.前端浏览器如何播放RTMP流画面] 本片文章只是起到抛砖引玉的作用,能从头到尾走通就行,并不做深入研究.为了让文章通俗易懂,尽量使用白话描述. 考虑到视频延 ...

  10. Kendo UI for jQuery使用教程:支持Web浏览器

    [Kendo UI for jQuery最新试用版下载] Kendo UI目前最新提供Kendo UI for jQuery.Kendo UI for Angular.Kendo UI Support ...

随机推荐

  1. 梦幻联动!金蝶&华为云面向大企业发布数据库联合解决方案

    摘要:近日,金蝶软件(中国)有限公司(以下简称"金蝶")携手华为云共同发布了金蝶云·星瀚.金蝶云·苍穹和GaussDB(for openGauss)数据库联合解决方案. 本文分享自 ...

  2. Centos7 怎么永久关闭防火墙

    1.连接到centos主机,然后输入命令"systemctl status firewalld.service"并按下回车键. 2.然后在下方可以查看得到 " activ ...

  3. Building wheel for opencv-python (pyproject.toml) ,安装命令增加 --verbose 参数

    安装时间较长,通过 --verbose 参数 可以看在不在继续 Mac 安装 paddlehub 出现 Building wheels for collected packages: opencv-p ...

  4. ThreadPoolExecutor 使用

    ThreadPoolExecutor 介绍 简写: package com.vipsoft.Thread; import java.util.concurrent.*; import java.uti ...

  5. 41. 干货系列从零用Rust编写负载均衡及代理,websocket与tcp的映射,WS与TCP互转

    wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代 ...

  6. Codeforce 318A - Even Odds(数学水题)

    Being a nonconformist, Volodya is displeased with the current state of things, particularly with the ...

  7. AtCoder Beginner Contest 177 (个人题解,C后缀和,D并查集,E质因数分解)

    补题链接:Here A - Don't be late 题意:高桥(Takahashi )现在要去距离家 \(D\) 米的地方面基,请问如果以最高速度 \(S\) 能否再 \(T\) 时刻准时到达? ...

  8. 揭秘 vivo 如何打造千万级 DAU 活动中台 - 启航篇

    本文首发于 vivo互联网技术 微信公众号 链接:  https://mp.weixin.qq.com/s/Ka1pjJKuFwuVL8B-t7CwuA作者:悟空中台研发团队 vivo大厦(南京) 一 ...

  9. 《3D编程模式》写书-第4次记录

    大家好,这段时间我完成了"再看设计原则"的初稿,包括了设计基础.单一职责原则.依赖倒置原则.接口隔离原则.合成复用原则.最少知识原则.开闭原则 目前我已经完成了所有的初稿,后面会进 ...

  10. C# 通过ServiceStack 操作Redis——Hash类型的使用及示例

    接着上一篇,下面转到hash类型的代码使用 Hash:结构 key-key-value,通过索引快速定位到指定元素的,可直接修改某个字段 /// <summary> /// Hash:类似 ...