Lighthouse的使用与Google的移动端最佳实践
Lighthouse
是一个Google开源的自动化工具,主要用于改进网络应用(移动端)的质量。目前测试项包括页面性能
、PWA
、可访问性(无障碍)
、最佳实践
、SEO
。Lighthouse会对各个测试项的结果打分,并给出优化建议,这些打分标准和优化建议可以视为Google的网页最佳实践。
使用入门
运行Lighthouse的方式有三种:在开发者工具(Devtools)的Audits,作为Chrome拓展程序使用,或者作为命令行工具使用。Chrome开发者工具不需要额外安装,和扩展程序一样提供了一个用户友好的界面,方便读取报告;扩展程序相对于开发者工具的优势是更及时,不用等待Chrome发版就能体验到最新的功能;命令行工具可以将Lighthouse集成到持续集成系统。
开发者工具
仅能在Chrome60及以上使用,因为之前版本的Chrome的开发者工具的audits面板还不是Lighthouse。 extension.png audits.png通过右上角的菜单或者快捷键(command+option+i)打开开发者工具,然后选择audits面板,点击Perform an audits会弹出一个options面板勾选测试项然后点击Run audits即可。
Chrome拓展程序
extension.pnggenerate.png
在右上角或者菜单里点击图中图标,Options可以配置测试项目,点击Generate report即可测试。
命令行工具
安装:
npm install -g lighthouse
# or use yarn:
# yarn global add lighthouse
使用:
lighthouse https://example.com
配置项:
$ lighthouse --help
lighthouse <url>
Logging:
--verbose Displays verbose logging [boolean]
--quiet Displays no progress, debug logs or errors [boolean]
Configuration:
--save-assets Save the trace contents & screenshots to disk [boolean]
--list-all-audits Prints a list of all available audits and exits [boolean]
--list-trace-categories Prints a list of all required trace categories and exits [boolean]
--additional-trace-categories Additional categories to capture with the trace (comma-delimited).
--config-path The path to the config JSON.
--chrome-flags Custom flags to pass to Chrome (space-delimited). For a full list of flags, see
http://peter.sh/experiments/chromium-command-line-switches/.
Environment variables:
CHROME_PATH: Explicit path of intended Chrome binary. If set must point to an executable of a build of
Chromium version 54.0 or later. By default, any detected Chrome Canary or Chrome (stable) will be launched.
[default: ""]
--perf Use a performance-test-only configuration [boolean]
--port The port to use for the debugging protocol. Use 0 for a random port [default: 0]
--hostname The hostname to use for the debugging protocol. [default: "localhost"]
--max-wait-for-load The timeout (in milliseconds) to wait before the page is considered done loading and the run should continue.
WARNING: Very high values can lead to large traces and instability [default: 45000]
--enable-error-reporting Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More:
https://git.io/vFFTO
--gather-mode, -G Collect artifacts from a connected browser and save to disk. If audit-mode is not also enabled, the run will quit
early. [boolean]
--audit-mode, -A Process saved artifacts from disk [boolean]
Output:
--output Reporter for the results, supports multiple values [choices: "json", "html", "domhtml"] [default: "domhtml"]
--output-path The file path to output the results. Use 'stdout' to write to stdout.
If using JSON output, default is stdout.
If using HTML output, default is a file in the working directory with a name based on the test URL and date.
If using multiple outputs, --output-path is ignored.
Example: --output-path=./lighthouse-results.html
--view Open HTML report in your browser [boolean]
Options:
--help Show help [boolean]
--version Show version number [boolean]
--blocked-url-patterns Block any network requests to the specified URL patterns [array]
--disable-storage-reset Disable clearing the browser cache and other storage APIs before a run [boolean]
--disable-device-emulation Disable Nexus 5X emulation [boolean]
--disable-cpu-throttling Disable CPU throttling [boolean] [default: false]
--disable-network-throttling Disable network throttling [boolean]
--extra-headers Set extra HTTP Headers to pass with request [string]
Examples:
lighthouse <url> --view Opens the HTML report in a browser after the run completes
lighthouse <url> --config-path=./myconfig.js Runs Lighthouse with your own configuration: custom audits, report
generation, etc.
lighthouse <url> --output=json --output-path=./report.json --save-assets Save trace, screenshots, and named JSON report.
lighthouse <url> --disable-device-emulation --disable-network-throttling Disable device emulation
lighthouse <url> --chrome-flags="--window-size=412,732" Launch Chrome with a specific window size
lighthouse <url> --quiet --chrome-flags="--headless" Launch Headless Chrome, turn off logging
lighthouse <url> --extra-headers "{\"Cookie\":\"monster=blue\"}" Stringify\'d JSON HTTP Header key/value pairs to send in requests
lighthouse <url> --extra-headers=./path/to/file.json Path to JSON file of HTTP Header key/value pairs to send in requests
For more information on Lighthouse, see https://developers.google.com/web/tools/lighthouse/.
测试结果示例
result.png
最佳实践
这些最佳实践主要针对移动端或者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>
地址栏颜色应该和品牌颜色、网页主题匹配
address.png就是地址栏的背景颜色应该和品牌颜色一致
通过meta标签实现的:
<meta name="theme-color" content="#ff6633">
不过仅在认可这个meta的浏览器上有效,比如Chrome for Android,实测pc、ios的Chrome、Safari无效。
bilibili.png
如果场景能用上还是能提高一些用户体验的,避免了地址栏突兀。
避免使用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可以分析出所有的可访问性问题
按钮有一个可访问的名称
没有名字的按钮对依赖屏幕阅读器的用户不可用。当一个按钮没有名字时,屏幕阅读器会宣布“按钮”。
对<button>元素和role="button"的元素:
- 设置元素的内部文本
- 设置aria-label属性
- 将该aria-labelledby属性设置为屏幕阅读器可见的文本元素。
对于<input type = "button">的元素:
- 设置value属性
- 设置aria-label属性
- 设置aria-labelledby属性
对于<input type="submit">和<input type="rest">:
- 设置value属性,或省略它。浏览器在value省略时赋予"submit"或"reset"的默认值
- 设置aria-label属性
- 设置aria-labelledby属性
页面在其脚本不可用时包含一些内容
基本内容和页面功能不应该依赖于CSS或JS。对于必需依赖JavaScript的页面,一种方法是使用一个<noscript>元素,以提醒用户此页面需要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字段
content.png
预计输入延迟时间
输入响应能力对用户如何看待应用的性能起着关键作用。应用有100毫秒的时间响应用户输入。如果超过此时间,用户就会认为应用反应迟缓。
优化代码在浏览器中的运行方式:
- 对于动画效果的实现,避免使用setTimeout或setInterval,请使用requestAnimationFrame
- 将长时间运行的JavaScript从主线程移动到Web Worker
- 使用micro-tasks来执行对多个帧的DOM更改
- 使用Chrome DevTools的Timeline和Javascript分析器来评估JavaScript的影响。
降低选择器的复杂性(例如:nth-of-type、:nth-child);使用以类为中心的方法,例如BEM,这有一篇BEM的教程 - 尽可能避免布局操作,对“几何属性”(如宽度、高度左侧或顶部)的更改都需要布局计算。布局几乎总是作用到整个文档,如果有大量元素,会消耗很长时间来计算出所有元素的位置和尺寸。
-
避免强制同步布局
render.png
首先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”。 rendering.png
- 每一个表单元素都应该有一个label
label阐明了表单元素的用途。虽然每个元素的目的对于有视觉的用户来说可能是显而易见的,但对于依靠屏幕阅读器的用户来说并非如此。有四种方式可以实现:
- 隐含标签
<label>First Name <input type="text"/></label>
- 显式标签
<label for="first">First Name <input type="text" id="first"/></label>
- aria-label
<button class="hamburger-menu" aria-label="menu">...</button>
- aria-labelledby
<span id="foo">Select seat:</span>
<custom-dropdown aria-labelledby="foo">...</custom-dropdown>
- 每个图像都有一个alt属性
信息性图像应该具有alt包含该图像内容的文本描述的属性。屏幕阅读器使视觉障碍的用户能够通过将文本内容转换为可以使用的表格(如合成语音或盲文)来使用您的网站。屏幕阅读器无法转换图像。因此,如果您的图片包含重要信息,那么视觉障碍用户无法获取该信息。
可以在DevTools的Console选项卡中使用以下命令来查找没有alt属性的图片
$$('img:not([alt])');
在Console中$$()相当于document.querySelectorAll()
- 配置HTML的Viewport meta标签
如果没有Viewport meta标签,移动设备将以典型的桌面设备屏幕宽度渲染页面,然后对页面进行缩放以适合移动设备屏幕。通过Viewport meta标签可以控制宽度和缩放比例。
配置视口 设置视口
width=device-width键值对将视口宽度设置为设备宽度。在访问页面时,initial-scale=1键值对设置初始缩放级别。
<head>
...
<meta name="viewport" content="width=device-width, initial-scale=1">
...
</head>
- 压缩图片(仅针对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-breakpoint.png
避免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});
参考资料
- https://developers.google.com/web/tools/lighthouse/
- https://developers.google.com/web/fundamentals/performance/rail
- https://developers.google.com/web/fundamentals/performance/rendering/
- https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics
欢迎访问我的博客