使用 WebPageTest 识别和修复布局不稳定问题的分步演示。
在之前的一篇文章中,我介绍了如何在 WebPageTest 中衡量累积布局偏移 (CLS)。CLS 是所有布局偏移的汇总,因此在本文中,我想深入探究并检查网页上的每个单独的布局偏移,以便了解可能导致不稳定性的原因,并实际尝试解决问题。
衡量布局偏移
使用 Layout Instability API,我们可以获取网页上所有布局偏移事件的列表:
new Promise(resolve => {
new PerformanceObserver(list => {
resolve(list.getEntries().filter(entry => !entry.hadRecentInput));
}).observe({type: "layout-shift", buffered: true});
}).then(console.log);
这会生成一系列不带输入事件的布局偏移:
[
{
"name": "",
"entryType": "layout-shift",
"startTime": 210.78500000294298,
"duration": 0,
"value": 0.0001045969445437389,
"hadRecentInput": false,
"lastInputTime": 0
}
]
在此示例中,在 210 毫秒时出现了一次非常微小的偏移,为 0.01%。
了解偏移的时间和严重程度有助于缩小可能导致偏移的原因范围。我们返回 WebPageTest 实验室环境,进行更多测试。
在 WebPageTest 中衡量布局偏移
与在 WebPageTest 中衡量 CLS 类似,衡量各个布局偏移也需要使用自定义指标。幸运的是,现在 Chrome 77 已是稳定版,因此该过程更为简单。布局不稳定性 API 默认处于启用状态,因此您应该能够在 Chrome 77 中的任何网站上执行该 JS 代码段,并立即获得结果。在 WebPageTest 中,您可以使用默认的 Chrome 浏览器,而无需担心命令行标志或使用 Canary 版。
因此,我们来修改该脚本,为 WebPageTest 生成自定义指标:
[LayoutShifts]
return new Promise(resolve => {
new PerformanceObserver(list => {
resolve(JSON.stringify(list.getEntries().filter(entry => !entry.hadRecentInput)));
}).observe({type: "layout-shift", buffered: true});
});
此脚本中的 promise 会解析为数组的 JSON 表示法,而不是数组本身。这是因为自定义指标只能生成字符串或数字等基元数据类型。
我将使用 ismyhostfastyet.com 网站进行测试,该网站是我构建的,用于比较网站托管服务的实际加载性能。
确定布局不稳定的原因
在结果中,我们可以看到 LayoutShifts 自定义指标的值为:
[
{
"name": "",
"entryType": "layout-shift",
"startTime": 3087.2349999990547,
"duration": 0,
"value": 0.3422101449275362,
"hadRecentInput": false,
"lastInputTime": 0
}
]
总而言之,在 3087 毫秒时发生了一次 34.2% 的布局偏移。为了帮助找出罪魁祸首,我们来使用 WebPageTest 的影片片段视图。
滚动到影片片段中大约 3 秒处,我们可以清楚地看到导致布局偏移 34% 的原因:彩色表格。该网站会异步提取 JSON 文件,然后将其渲染到表格中。该表最初是空的,因此在结果加载时等待填充该表会导致偏移。
但这还不是全部。当页面在约 4.3 秒内完全显示时,我们可以看到“我的主机速度够快吗?”页面的 <h1>
突然出现。这是因为该网站使用的是 Web 字体,并且未采取任何措施来优化呈现效果。发生这种情况时,布局实际上似乎不会发生变化,但用户仍然需要等待很长时间才能阅读标题,这会给用户带来糟糕的体验。
解决布局不稳定问题
现在,我们已经知道异步生成的表格导致了视口的三分之一发生了偏移,接下来就该解决这个问题了。在 JSON 结果实际加载之前,我们不知道表格的内容,但我们仍然可以使用某种占位符数据填充表格,以便在渲染 DOM 时布局本身相对稳定。
以下是用于生成占位符数据的代码:
function getRandomFiller(maxLength) {
var filler = '█';
var len = Math.ceil(Math.random() * maxLength);
return new Array(len).fill(filler).join('');
}
function getRandomDistribution() {
var fast = Math.random();
var avg = (1 - fast) * Math.random();
var slow = 1 - (fast + avg);
return [fast, avg, slow];
}
// Temporary placeholder data.
window.data = [];
for (var i = 0; i < 36; i++) {
var [fast, avg, slow] = getRandomDistribution();
window.data.push({
platform: getRandomFiller(10),
client: getRandomFiller(5),
n: getRandomFiller(1),
fast,
avg,
slow
});
}
updateResultsTable(sortResults(window.data, 'fast'));
占位符数据会先随机生成,然后再进行排序。它包含随机重复多次的“█”字符,用于为文本创建视觉占位符,以及三个主要值的随机生成分布。我还添加了一些样式,以使表格中的所有颜色都去饱和,以明确表明数据尚未完全加载。
您使用的占位符的外观对布局稳定性没有影响。占位符的用途是让用户知道内容即将显示,并且页面没有损坏。
在 JSON 数据加载期间,占位符如下所示:
解决 Web 字体问题要简单得多。由于该网站使用的是 Google Fonts,因此我们只需在 CSS 请求中传入 display=swap
属性即可。就这些了。Fonts API 会在字体声明中添加 font-display: swap
样式,以便浏览器立即以回退字体渲染文本。下面是包含修复程序的相应标记:
<link href="https://fonts.googleapis.com/css?family=Chivo:900&display=swap" rel="stylesheet">
验证优化
通过 WebPageTest 重新运行该网页后,我们可以生成“改进前后”比较,以直观地显示差异并衡量新的布局不稳定程度:
[
{
"name": "",
"entryType": "layout-shift",
"startTime": 3070.9349999997357,
"duration": 0,
"value": 0.000050272187989256116,
"hadRecentInput": false,
"lastInputTime": 0
}
]
根据自定义指标,布局仍然会在 3071 毫秒(与之前大致相同的时间)发生偏移,但偏移的严重程度大大降低,为 0.005%。我可以接受。
从影片中还可以清楚地看到,<h1>
字体会立即回退为系统字体,以便用户更快地阅读。
总结
复杂的网站可能会比本例中出现更多布局偏移,但修复流程仍然相同:将布局不稳定指标添加到 WebPageTest,将结果与视觉加载影片片段进行交叉引用以确定罪魁祸首,然后使用占位符实现修复,以预留屏幕空间。
(补充一点)衡量真实用户遇到的布局不稳定问题
在优化前后对网页运行 WebPageTest 并看到某个指标有所提升,这固然令人欣喜,但真正重要的是,用户体验确实有所提升。这不正是我们一开始就努力改进网站的原因吗?
因此,如果我们能将传统的 Web 性能指标与真实用户的布局不稳定体验指标结合起来衡量,那就太棒了。这是优化反馈环路的关键部分,因为从现场获取数据有助于我们了解问题所在,以及我们的修复措施是否有积极作用。
除了收集您自己的布局不稳定性数据之外,您还可以查看 Chrome 用户体验报告,其中包含来自数百万个网站的真实用户体验的累积布局偏移数据。借助该工具,您可以了解自己(或竞争对手)的表现,也可以探索网络上布局不稳定的状态。