Skip to content

28 视觉:避免发生布局偏移影响用户交互

你好,我是三桥。

上节课,我们通过实例解决了视口中图片加载的问题,经过几轮优化,图片在视口中的显示速度得到了提升。

然而,在优化之前,视口中的图片加载还存在一个问题,那就是页面视觉稳定性都不太好。下图是上节课优化过程的时序图。

图片

上面四张时序图显示,图1是优化前的官网加载时序图,报告显示有严重的布局偏移问题,得分是0.7450。

这是因为图1中的图片是通过后台读取的富文本内容,而且没有设置图片的宽高,这就导致浏览器没有给图片预留布局空间。直到当图片加载时,底部内容才会被移出视口。

其它时序图没有出现布局偏移,因为所有图片都预设了宽高属性。

也就是说,视口中的元素,无论是图片还是文本,如果设置了宽高,正常情况下是不会有布局偏移的。

什么是CLS?

不知道你之前有没有遇到过下面的几种情况。

  • 阅读一篇新闻文章时,页面顶部的广告突然弹出,导致所有内容向下移动。这种被中断的感觉让人十分沮丧。
  • 通过手机H5页面购物时,购物车中有几件商品。你想更新商品数量,然后继续购物。但是你点击编辑后,页面布局改变了,手指误触了“删除按钮”而不是 “+1” 按钮。视觉位置突变,让使用体验非常糟糕。
  • 填写表单时,选择一个选项之后导致页面内容发生变化,不知道下一步该怎么填写,很迷茫。

可以说,布局偏移在网页加载期间非常常见,因为浏览器需要获取所有必要的资源来显示视口可见的内容,而且初始加载后也可能会发生布局偏移。

为了提供高质量的网页效果,避免这种糟糕的用户体验的网页出现在搜索结果中,谷歌搜索引擎提出了视觉稳定性指标,作为核心网页指标。

深入理解CLS

累积布局偏移(Cumulative Layout Shift,简称CLS)是一个网页指标,用于衡量在网页的整个生命周期内所有意外布局偏移的总分。

从基本概念来看,CLS有四个主要特点。

第一,涵盖网页的整个生命周期。无论是在加载过程中,还是用户交互过程中,都有可能发生CLS。

第二,网页布局偏移可能发生一次或多次。

第三,最后的偏移分数是所有布局偏移分数的总和。其中,布局偏移分数由影响比例和距离分数两个因素决定。计算公式为:$$LayoutShift Score = impact fraction * distance fraction$$

而偏移分数的总和即为:$$CLS = SUM(LS_Score1, LS_Score2, LS_Score3, ……)$$

第四,CLS值是一个分数,而不是时间值。

第五,只有在视口内的可见元素在渲染两帧之间相对于初始位置发生改变,才会被认为发生了布局偏移。

谷歌为CLS指标设定了一套衡量标准。分数低于0.1或没有这个值,就认为没有用户体验问题。如果分数小于0.25,那么页面就有改进的空间。当分数大于0.25时,就认为存在布局偏移问题,需要重点关注。

下图清晰地展示了不同分数区间的关系。

图片

有了这个得分,我们就可以在前端全链路方案中,设置一个阈值,通过这个阈值可以提醒前端同学用户的实际情况,从而初步判断哪个页面可能会影响用户体验。

虽然这个分数只是一个参考值,但在实际分析用户体验问题时,我们还需要通过开发者工具进行分析这个值,再下结论。接下来,我们将使用上一节课的例子来进一步解释CLS分数和实际的布局偏移效果。

分数与视觉的关系

我们再看一下网页加载时页面布局的变化效果,如下图所示。

图片

这张动图里,布局偏移出现了两次。

第一次布局偏移发生在头部和底部内容渲染完成后,底部区域突然向上移动,如下图所示。

图片

在整个偏移过程中,红色透明底在视口区域的占比超过67%,说明影响比例为0.67。移动距离大约为106像素,约占屏幕比例的12.5%。因此,通过公式计算得出的分数为0.08417。

然后,我们来看第二次布局偏移,这次的位置偏移更严重,如下图所示。

图片

从图中可以看出,底部区域已完全被移出屏幕视口。在偏移过程中,红色透明底在视口区域占比78.4%,所以布局偏移值为0.784。此次布局偏移得分是0.7450,影响比例接近于1。

最后,CLS累积偏移值是0.8291分,是由两次布局偏移分数相加得出的。

因此,我们可以得出这样一个结论: 元素偏移的位置距离越远,布局偏移值就越大;影响视口面积越大,其影响比例也就越高,布局偏移值也就越大。

接下来,我们继续探讨如何避免这些问题。

优化建议

想要避免这个问题,我们要确保在每一帧中,浏览器视口内的所有元素没有水平或垂直距离的移动。

这里有四种常见的优化建议。

图片尺寸问题

首先,我们需要确认浏览器视口中有多少图片,每张图片是否都已加上 widthheight 属性。如果没有,是否会导致布局偏移?我们可以通过开发者工具来确认这个问题。

以下代码为设置 widthheight 属性的示例。

<img
  src="cls-of-image.jpg"
  width="400"
  height="160"
  class="cls"
  alt="cls-of-image"
>

通过预设的 widthheight 属性,浏览器就能提前在网页上分配空间,最大限度地减少重排和重新布局的可能。

但是,对于移动设备,预设的宽高无法适配那么多种屏幕大小和分辨率,那应如何解决呢?

第一种方法是 设定宽高比

CSS属性 aspect-ratio 允许给盒子元素定义首选宽高比,它可以设定两个值,一个是auto,另一个是宽高比。如果为上述图片定义宽高比,参考代码如下。

.cls {
  aspect-ratio: auto 400 / 160;
}

对于未知尺寸的图片,我们可以通过宽高比告知浏览器分配页面空间。相对于未定义尺寸的图片,这种情况下的布局偏移会更小。不过,如果图片尺寸不同,加载后可能仍会出现布局偏移。

第二种方法是 使用自适应图片特性

srcsetimg 元素的新属性。它允许定义一个或多个图像候选地址,多地址之间以 , 分割。每张图片的候选地址包含图片URL、可选宽度描述符和像素密度描述符。

通过 srcset 属性,我们可以根据屏幕大小、分辨率去设置不同的图片格式,例如以下代码。

<img
  width="64"
  height="64"
  src="icon64px.png"
  srcset="icon32px.png 32w, icon64px.png 64w, icon-retina.png 2x icon-ultra.png 3x icon.svg"
  alt="cls of icon image"
/>

除了 srcset 属性可以支持多种图片,HTML还提供了 <picture> 元素来解决不同屏幕大小的响应式图片。它可以包含零或多个 <source> 元素,以及一个 <img> 元素来提供不同场景的图片地址。

如果其他 <source> 元素无法满足条件,那么就会使用 <img> 元素作为默认提供的图片。

下面是一个使用媒体查询特性在不同分辨率下应用不同格式图片的代码示例。

<picture>
  <source media="(max-width: 1024px)" srcset="icon-retina.png 2x" width="128" height="128" />
  <source media="(min-width: 800px)" srcset="icon64px.png 64w" width="64" height="64" />
  <img src="icon64px.png" alt="cls of icon image" width="64" height="64" />
</picture>

总的来说,为了避免布局偏移问题,尤其是移动设备,我们应尽可能提前定义视口中的图片尺寸,即使我们无法预知这些尺寸,也可以设定一个预设的宽高比,以便浏览器预留空间渲染图片。

对于我们已知尺寸的图片,我们可以根据设备、屏幕大小、分辨率等因素,采用响应式的方法,预定义不同的图片大小和格式。只有这样,我们才能杜绝由于没有设置图片宽高而导致的布局偏移。

动态内容

网页中的图片通常是通过异步加载的方式获取的,除此之外,还有许多内容是通过异步加载的,如广告位、第三方插件代码、动态插入DOM内容等等。

像我们看到的加载更多商品的商品列表,或者实时更新信息流内容等,都可能导致页面结构的变化。

特别是动态插入的广告,通常会在页面主要内容生成后加载。如果此时页面没有为广告位预留空间,就可能导致布局偏移。

有的页面也会通过嵌入 iframe 来引入第三方内容,也很容易发生布局偏移。相关的参考代码如下。

<iframe
    src="//player.bilibili.com/player.html?isOutside=true&aid=1004606622&bvid=BV1cx4y1W7ea&cid=1544415949&p=1"
    scrolling="no"
    border="0"
    frameborder="no"
    framespacing="0"
    allowfullscreen="true"
></iframe>

总的来说,动态插入页面内容是网页交互中最常见的行为。

想要避免由于动态内容导致的布局偏移,主要有四种方法。

首先,和优化图片加载基本相同,就是确保每个内容块都有预设的尺寸,或者通过宽高比来减少由于动态内容导致的布局偏移。

其次,避免在用户没有互动的情况下插入新内容。

第三,如果引入的是第三方代码,最好的方式是使用 div 包含它,并在这个 div 中预先定义第三方页面内容的尺寸。但是,我们有时无法预知 iframe 内容是否符合预设的宽高。这就需要我们在开发过程中多做多调试,确保 div 宽高和 iframe 内容的宽高保持一致。

最后,相比于视口最高处位置,在页面视口靠下的位置动态插入内容,可以有效降低布局偏移的影响。

动画问题

CSS动画可能是大部分前端同学最容易忽略的布局偏移问题。因为动画的复杂性和动态性可能掩盖了潜在的布局偏移问题。

例如,使用CSS动画改变元素位置时,因为浏览器必须重新计算元素位置,就可能会导致布局偏移。

为了避免这种情况,我们应该优先使用 transform 属性来改变元素的位置,而不是使用 toprightbottomleft 属性。 transform 属性不会导致布局偏移,因为它不会改变元素在文档流中的位置。

同样,如果我们在CSS动画中改变元素大小,也可能导致布局偏移。最佳做法是避免使用 widthheight 属性改变元素的大小,而使用 transform: scale()。这样就不会改变元素在文档中的位置,也就不会导致布局偏移。

总的来说,我们应在网页的整个生命周期里,避免使用CSS改变元素的大小和位置,尽量使用 transform 属性替代。

字体问题

我们在前面的章节提到,网络字体可能导致“无样式文本”闪烁问题,进而导致布局偏移。

解决这个问题的一种方法是预先设置 font-display 属性为 optional,这样,即使网络字体加载失败,浏览器也不会重新排版。

关于这方面的具体优化,我们在第27节课已经详细讲解过,这里就不再赘述。

总结

总结一下,累积布局偏移作为网页核心指标之一,其主要目的是衡量用户的真实交互体验。特别是在移动设备上,位置偏移和元素大小都可能导致布局偏移,页面内容在视口内的闪烁、跳动和移动,都可能让用户感到困扰。

只要CLS分数超过0.1,我们就需要重点关注布局偏移问题,用开发者工具找出布局偏移的位置,然后分析可能存在的问题。

当然,页面交互过程中的布局偏移是无法避免的,我们需要根据实际情况分析具体问题,最核心的解决方案是视口内的所有元素,包括图片和视频,应提前设置宽高尺寸,以确保视口内容符合用户的预期。但

但是,每个项目遇到的问题都不一样,如果真的无法避免某些布局偏移问题,我们应该考虑多种方案,让用户能感知即将发生的变化。同时,时刻关注CLS指标,通过开发者工具定位、排查布局偏移问题,找出有效的优化方案,让前端页面更友好,提升用户体验。

思考题

现在,我给你布置一道思考题。

在你负责的前端项目中,尝试找出布局偏移的例子,并分析是否可以进行优化?如果是业务需要的布局偏移情况,你采取了哪些交互来解决用户体验问题?

欢迎你在留言区和我交流。如果觉得有所收获,也可以把课程分享给更多的朋友一起学习。我们下节课见!