原文地址:Lighthouse 的使用与 Google 的移动端最佳实践
Lighthouse
是一个 Google 开源的自动化工具,主要用于改进网络应用(移动端)的质量。目前测试项包括页面性能
、PWA
、可访问性(无障碍)
、最佳实践
、SEO
。Lighthouse 会对各个测试项的结果打分,并给出优化建议,这些打分标准和优化建议可以视为 Google 的网页最佳实践。
使用入门
运行 Lighthouse 的方式有三种:在开发者工具(Devtools)的 Audits,作为 Chrome 拓展程序使用,或者作为命令行工具使用。Chrome 开发者工具不需要额外安装,和扩展程序一样提供了一个用户友好的界面,方便读取报告;扩展程序相对于开发者工具的优势是更及时,不用等待 Chrome 发版就能体验到最新的功能;命令行工具可以将 Lighthouse 集成到持续集成系统。
开发者工具
仅能在 Chrome60 及以上使用,因为之前版本的 Chrome 的开发者工具的 audits 面板还不是 Lighthouse。
通过右上角的菜单或者快捷键(command+option+i)打开开发者工具,然后选择 audits 面板,点击 Perform an audits 会弹出一个 options 面板勾选测试项然后点击 Run audits 即可。
Chrome 拓展程序
在右上角或者菜单里点击图中图标,Options 可以配置测试项目,点击 Generate report 即可测试。
命令行工具
安装:
npm install -g lighthouse
# or use yarn:
# yarn global add lighthouse
使用:
lighthouse https://example.com
配置项:
通过如下命令查看:
$ lighthouse --help
测试结果示例
最佳实践
这些最佳实践主要针对移动端或者 Web 应用。某些技术对浏览器版本要求较高,用之前最好在Can I use、MDN上查一下浏览器支持情况
打开外部链接使用 rel=“noopener”
当页面使用 target="_blank" 跳转至另一个页面时,新页面将与您的页面在同一个进程上运行。 如果新页面正在执行开销极大的 JavaScript,您的页面性能可能会受影响。最重要的是 target="_blank”也是一个安全漏洞
。新页面可以通过 window.opener 访问旧页面的 window 对象,并且它可以使用 window.opener.location=newURL 将旧页面导航至不同的网址。所以当在新窗口或标签中打开一个外部链接时,应该始终加上 rel=“noopener”,例如:
<a href="https://examplepetstore.com" target="_blank" rel="noopener">...</a>
地址栏颜色应该和品牌颜色、网页主题匹配
就是地址栏的背景颜色应该和品牌颜色一致
通过 meta 标签实现的:
<meta name="theme-color" content="#ff6633">
不过仅在认可这个 meta 的浏览器上有效,比如 Chrome for Android,实测 pc、ios 的 Chrome、Safari 无效。
如果场景能用上还是能提高一些用户体验的,避免了地址栏突兀。
避免使用 AppCache
AppCache 已被废弃
考虑使用 service worker 的Cache API,另外现在 ios 11.3 也支持了 service worker,未来一两年应该有很大发展。
避免使用 console.time()
如果使用 console.time()测试页面性能,请考虑使用 User Timing API。其优势包括:
-
高分辨率时间戳
-
可导出的计时数据
-
与 Chrome Devtools TImeline 相集成。在 Timeline 录制期间调用 User Timing 函数 performance.measure() 时,DevTools 自动将此测量结果添加到 Timeline 的结果中。
将 console.time()替换为 performance.mark()。如果需要测量两个 label 之间经过的时间,则使用 performance.measure()。User Timing API// 获得命名时间戳
window.performance.mark(‘mark_fully_loaded’);
// 获得命名时间戳之间的时间间隔或者与 PerformanceTiming 的时间间隔
window.performance.measure(‘measure_load_from_dom’, ‘domComplete’, ‘mark_fully_loaded’);
避免使用 Date.now()
考虑改用 performance.now()代替 Date.now()。performance.now()可提供较高的时间戳分辨率,并始终以恒定的速率增加,它不受系统时钟(可以调整)的影响。performance.now()
// 获取相对于navigationStart属性中的时间戳为起点开始计时的精确到千分之一毫秒的时间戳
window.performance.now()
避免弃用的 API
已弃用的 API 计划从 Chrome 中移除,使用这些 API 后,被删除后将导致网页出错。查看 Chrome 平台状态
避免使用 document.write()
对于网速较慢(2G、3G 或较慢的 WLAN)的用户,外部脚本通过 document.write()动态注入会使页面内容的显示延迟数十秒。
避免巨大的网络负载
延迟请求直到需要它们
启用文本压缩
压缩 HTML、JS 和 CSS
使用 Webp 而不是 JPEG 或 PNG
将 JPEG 图像的压缩级别设置为 85
缓存请求
避免使用 mutation events
以下 mutation events 会损害性能,在 DOM 事件规范中已经弃用:
- DOMAttrModified
- DOMAttributeNameChanged
- DOMCharacterDataModified
- DOMElementNameChanged
- DOMNodeInserted
- DOMNodeInsertedIntoDocument
- DOMNodeRemoved
- DOMNodeRemovedFromDocument
- DOMSubtreeModified
建议将每个 mutation events 替换成MutationObserver
避免使用旧版 CSS Flexbox
2009 年的旧 Flexbox 规范已弃用,其速度比最新的规范慢 2.3 倍。将页面中的 display:box 及以 box 开头的每个属性替换成标准的 Flexbox 属性。
避免在页面加载时自动请求地理位置
页面在加载时自动请求用户位置会使用户不信任页面或感到困惑。应将此请求与用户的手势进行关联,而不是在页面加载时自动请求用户的位置。
避免在页面加载时自动请求通知权限
好的通知需要做到及时、相关且准确。如果页面在加载时要求权限以发送通知,则这些通知可能与您的用户无关或者不是他们的精准需求。为提高用户体验,最好是向用户发送特定类型的通知,并在他们选择加入后显示权限请求。
避免使用 Web SQL
背景和前景应该有足够的对比度
低对比度文本对于许多用户来说很难或不可能读取
使用 Chrome 扩展程序aXe可以分析出所有的可访问性问题
按钮有一个可访问的名称
没有名字的按钮对依赖屏幕阅读器的用户不可用。当一个按钮没有名字时,屏幕阅读器会宣布“按钮”。
对元素和 role="button"的元素:
- 设置元素的内部文本
- 设置 aria-label 属性
- 将该 aria-labelledby 属性设置为屏幕阅读器可见的文本元素。
对于的元素:
- 设置 value 属性
- 设置 aria-label 属性
- 设置 aria-labelledby 属性
对于和:
- 设置 value 属性,或省略它。浏览器在 value 省略时赋予"submit"或"reset"的默认值
- 设置 aria-label 属性
- 设置 aria-labelledby 属性
页面在其脚本不可用时包含一些内容
基本内容和页面功能不应该依赖于 CSS 或 JS。对于必需依赖 JavaScript 的页面,一种方法是使用一个
元素,以提醒用户此页面需要 JavaScript。
优化关键渲染路径
将关键资源数降至最低:消除关键资源、延迟关键资源的下载并将它们标记为不同步等。
优化关键字节数以缩短下载时间。
优化其余关键资源的加载顺序:尽早下载所有关键资产,以缩短关键路径长度。
避免长宽比不正确的图像
如果渲染的图像与其源文件中的长宽比不同,则呈现的图像可能看起来失真,产生不愉悦的用户体验。
- 避免将元素的宽度或高度设置为可变大小的容器的百分比。
- 避免设置不同于源图像尺寸的显式宽度或高度值。
- 考虑使用css-aspect-ratio或 Aspect Ratio Boxes来帮助保留宽高比。
- 如果可能的话,在 HTML 中指定图片的宽度和高度是一个很好的做法,这样浏览器就可以为图片分配空间,这样可以防止页面在加载时跳过。在 HTML 中而不是 CSS 中指定宽度和高度是更理想的,因为浏览器在解析 CSS 之前分配空间。实际上,如果您使用响应式图像,则此方法可能很困难,因为在知道视口尺寸之前无法指定宽度和高度。
启用文本压缩
如果浏览器支持,则配置服务器以使用Brotli压缩响应。Brotli 比 GZIP 可以节省更多的流量。如果不支持 Brotli 则使用 GZIP。在 Chrome DevTools 检查响应是否被压缩:
- 打开 DevTools 的 Network 面板
- 点击指定的回复的请求。
- 点击 Headers 选项卡
- 检查 Response Headers 中 content-heading 字段
预计输入延迟时间
输入响应能力对用户如何看待应用的性能起着关键作用。应用有 100 毫秒的时间响应用户输入。如果超过此时间,用户就会认为应用反应迟缓。
优化代码在浏览器中的运行方式:
- 对于动画效果的实现,避免使用 setTimeout 或 setInterval,请使用 requestAnimationFrame
- 将长时间运行的 JavaScript 从主线程移动到 Web Worker
- 使用 micro-tasks 来执行对多个帧的 DOM 更改
- 使用 Chrome DevTools 的 Timeline 和 Javascript 分析器来评估 JavaScript 的影响。
降低选择器的复杂性(例如:nth-of-type、:nth-child);使用以类为中心的方法,例如 BEM,这有一篇BEM 的教程 - 尽可能避免布局操作,对“几何属性”(如宽度、高度左侧或顶部)的更改都需要布局计算。布局几乎总是作用到整个文档,如果有大量元素,会消耗很长时间来计算出所有元素的位置和尺寸。
- 避免强制同步布局
首先 JavaScript 运行,然后计算样式,然后布局。但是,可以使用 JavaScript 强制浏览器提前执行布局。这被称为强制同步布局。在 JavaScript 运行时,来自上一帧的所有旧布局值是已知的,并且可供查询。因此,如果在帧的开头写出一个元素的高度是没有问题的,但是在查询高度之前,已经更改其样式,如下列代码。,就会强制页面计算返回正确的高度。这是不必要的,并且开销很大。始终应先批量读取样式并执行,然后执行任何写操作。
function logBoxHeight() {
box.classList.add('super-big');
// Gets the height of the box in pixels
// and logs it out.
console.log(box.offsetHeight);
}
-
除 transform 或 opacity 属性之外,更改任何属性始终都会触发绘制。可以使用 Chrome DevTools 来快速确定正在绘制的区域。打开 DevTools,按下键盘上的 Esc 键。在出现的面板中,转到“rendering”标签,然后选中“Show paint rectangles”。
-
每一个表单元素都应该有一个 label
label 阐明了表单元素的用途。虽然每个元素的目的对于有视觉的用户来说可能是显而易见的,但对于依靠屏幕阅读器的用户来说并非如此。有四种方式可以实现:
-
隐含标签
-
显式标签
-
aria-label
…
-
aria-labelledby
Select seat:
…
-
每个图像都有一个 alt 属性
信息性图像应该具有 alt 包含该图像内容的文本描述的属性。屏幕阅读器使视觉障碍的用户能够通过将文本内容转换为可以使用的表格(如合成语音或盲文)来使用您的网站。屏幕阅读器无法转换图像。因此,如果您的图片包含重要信息,那么视觉障碍用户无法获取该信息。
可以在 DevTools 的 Console 选项卡中使用以下命令来查找没有 alt 属性的图片
在 Console 中$$()相当于 document.querySelectorAll()
-
配置 HTML 的 Viewport meta 标签
... ...
如果没有 Viewport meta 标签,移动设备将以典型的桌面设备屏幕宽度渲染页面,然后对页面进行缩放以适合移动设备屏幕。通过 Viewport meta 标签可以控制宽度和缩放比例。
配置视口 设置视口
width=device-width 键值对将视口宽度设置为设备宽度。在访问页面时,initial-scale=1 键值对设置初始缩放级别。 -
压缩图片(仅针对 JPEG)
将每个图像的压缩级别设置为 85 或更低,像TinyJPG这样的 Web 服务可以帮助自动化图像优化的过程
避免页面存在不成功的 HTTP 状态码
搜索引擎可能无法正确索引返回不成功的 HTTP 状态码的页面。
允许用户粘贴到密码字段中
密码粘贴提高了安全性,因为它使用户能够使用密码管理器。密码管理员通常为用户生成强密码,安全地存储密码,然后在用户需要登录时自动将其粘贴到密码字段中。
删除阻止用户粘贴到密码字段的代码。使用事件断点中的 Clipboard paste 来打断点,可以快速找到阻止粘贴密码的代码。比如下列这种阻止粘贴密码的代码:
let input = document.querySelector('input');
input.addEventListener('paste', (e) => {
e.preventDefault(); // This is what prevents pasting.
});
避免 DOM 过大
大型的 DOM 树会以多种方式降低页面性能:
- 网络效率和负载性能,如果你的服务器发送一个大的 DOM 树,你可能会运送大量不必要的字节。这也可能会减慢页面加载时间,因为浏览器可能会解析许多没有显示在屏幕上的节点。
- 运行时性能。当用户和脚本与页面交互时,浏览器必须不断重新计算节点的位置和样式。一个大的 DOM 树与复杂的样式规则相结合可能会严重减慢渲染速度。
内存性能。如果使用通用查询选择器(例如,document.querySelectorAll(‘li’) 您可能会无意中将引用存储到大量的节点),这可能会压倒用户设备的内存功能。
一个最佳的 DOM 树:
- 总共少于 1500 个节点。
- 最大深度为 32 个节点。
- 没有超过 60 个子节点的父节点。
- 一般来说,只需要在需要时寻找创建 DOM 节点的方法,并在不再需要时将其销毁。
如果你不能避免一个大型的 DOM 树,改善渲染性能的另一种方法是简化你的 CSS 选择器。请参阅减少风格计算的范围和复杂性。
使用被动事件监听器以提升滚动性能
被动事件是新兴的 Web 标准,可以显著提高滚动性能,尤其在移动设备上。当使用 touch 事件监听器(scroll 事件不存在这个问题)进行滚动时,因为浏览器不知道你是否会取消滚动,它们总是等待监听器执行完毕后才开始滚动,这样就造成了明显的延迟。事件监听器 options 中使用 passive:true 表明监听器永远不会取消滚动,这样浏览器就可以立即滚动。
在支持被动事件侦听器的浏览器中,将侦听器标记为 passive 即可:
document.addEventListener('touchstart', onTouchStart, {passive: true});