你的骨架屏用对了吗?

骨架屏(skeleton screen),每个前端都爱用。它不仅能提升用户体验,制作起来也简单,数据上(FP,首次渲染)也好看,基本上是无脑上不会有错的。

如今大部分关于骨架屏的探讨在于如何借助工具来快速生成骨架屏,虽然这种流水线式的骨架屏即使不借助工具,也不会耗费开发太多时间。反而大家不太会去关注,骨架屏应该做成什么样,它的底层逻辑是什么,什么是好的骨架屏?

本文通过回顾骨架屏的前世今生,并结合货拉拉的真实案例,来探讨下骨架屏背后的思想,以及如何做出更好的骨架屏。

打破沉寂

界面没有反馈,会让用户觉得不安,这是一个自人机交互诞生以来就存在的问题。这在如今移动互联网场景下,就是我们常说的「白屏」问题。所以,「自古」以来,遇到需要用户等待的场景,都要搞点小动画来打破尴尬,例如大家熟悉的「无限滚动条」和「转圈圈」:

早期的货拉拉:

一方面告诉用户「我还在运转,没有出故障」,另一方面吸引用户注意力,让等待的时间不那么无聊。

骨架屏诞生

但是时间久了,大家便不满足于这些小动画,它既不反映实际的加载进度,和后面要出现的内容也毫无关系,如果停留时间过久反而让用户更加疲惫,觉得时间过得更慢了。随着移动互联网时代到来,人们对体验越来越重视,于是体验更好的「骨架屏」就被发明了出来。

根据当前能查到的资料,最早介绍骨架屏的文章出现于 2013年:LukeW | Mobile Design Details: Avoid The Spinner

顾名思义,在数据和资源还在加载中时,先展示一个页面的「骨架」,让用户知道接下来的页面大致长什么样,然后逐步的填充里面的内容。一般来说,应用在加载数据和资源时比较耗时,但是页面的框架结构是可以预先确定好的,把这部分信息通过骨架屏尽快传达给用户,让用户感受到页面的进展,产生「页面马上就好了」的预期(为何我联想到了PDD再砍一刀),同时也使整个加载过程更连贯,界面变化不那么突兀。

总结一下,骨架屏的优势在于:

  • 及时反馈,让用户感受到进展。
  • 连贯性,细粒度变化,不突兀。

粗糙的骨架屏

以其独特的优势,骨架屏迅速开始流行起来,并且催生了看似丰富的生态。

一些 UI 库提供了现成的骨架屏组件,实现了「拿来即用」。

有人觉得组件库还是太麻烦,于是开发了自动化工具,可以根据现有的页面自动生成与之匹配的骨架屏。

这些眼花缭乱的「生态」吸引了大部分人的注意力,以至于在谈论到骨架屏时,大家都在比拼如何「快速」、「省事」地给应用装上一个骨架屏。

案例1:

一个应用原来的加载过程是:

空白 -> 转圈圈 -> 内容

老板提议用骨架屏优化一下,开发用 UI 组件库很快速的就搞定了,于是加载过程变成了:

空白 -> 转圈圈 -> 骨架屏 ->内容

老板:???为什么还有转圈圈

开发:你就说我做的快不快吧!

老板:能不能把转圈圈也搞成骨架屏啊?

开发:这个骨架屏是个 react 组件,你懂不?react 得在 mount 之后才能看到,mount 之前还是得用转圈圈......

老板:我虽然不懂 react 但我略懂 html,你直接把骨架屏放做到原生 html 里不行吗?

开发:得加时间...

可见,用了工具,就会受限于工具。骨架屏的意义之一就在于「以最快的速度给用户带来反馈」,好的做法是直接放到原生的 html 模板中。但是工具的泛滥让大家变懒了,于是变成了「以最快的速度应付老板的需求」。

案例2:

小程序的开发工具,也集成了自动生成骨架屏的功能:

生成的骨架屏类似这样:

😂 这能用?好吧没关系,我们基于它生成的代码进行修改,也能省不少事,打开代码看看:

额这🤮

小程序的包体积本来就有上限,这下雪上加霜。

...

finally 我们终于拿到了一个还不错的骨架屏,把它装上试试:

🤔感觉有点突兀呢,而且好像等的时间更长了?

之前是哪个接口先返回就先渲染对应的模块,虽然有点杂乱吧,但好歹是渐进式的,让人能先看到一些东西。用了这个所谓「骨架屏」,反而要等到最慢的接口返回以后,才「啪的一下」全部出现。

这与其说是骨架屏,不如说是「启动屏」(splash screen),还不如转圈圈,转圈圈起码不会把整个页面都挡住。

本来是想省事,结果更加费事,效果还没有达到预期。

好的骨架屏

「什么都是现成的只会害了你」,「无脑」让人丢掉了思考,忽视了骨架屏的本意。

回顾下骨架屏的核心思想:及时反馈、连贯性,让我们以此出发来做一个真正有效的骨架屏。

更细粒度的变化

可能骨架屏的「屏」字产生了误导,让大家以为骨架屏一定是覆盖了整个屏幕。其实更应该关注的是「骨架」,即整个加载流程是一个先有骨架然后逐步的填充的过程。

如下所示(为了展示效果,特意放慢了速度):

可以看出,我们把一个大的骨架「屏」拆分到了各个组件中,由各个模块独立控制自己的骨架屏,相较于原始的随接口实时加载,变化更加丝滑流程,让人产生确定的期望,不会有突兀感。

彩色骨架屏

骨架屏往往被设计成淡灰色,因为比较不显眼,不会占用用户过多注意力,后面无论被什么样的内容替换掉,都不会太突兀。

但如果要加载的内容本身有比较确定的颜色,那骨架屏也可以是彩色的。如下图,包括颜色和一些铁定不会变的文字,都可以是骨架屏的一部分:

这体现了骨架屏的「及时反馈」原则,只要是确定的,准备好的东西,就尽快的反馈给用户。

骨架屏动效

回顾前面说的「打破沉寂」,会动的画面会减少用户的焦虑,骨架屏如果再动起来是不是更好。

例如这种闪烁的光影,好像是比不会动的要好那么一点点?

但是这种闪烁的光影没有体现出连贯性,它只是一个大号的无限进度条或转圈圈。

一种比较好的做法是把骨架屏本身的加载也变成连贯的动画:

这个骨架屏有一个渐进出现的效果,你可能意识不到这个动画花费了400毫秒,用户往往会忽略这个时间,不认为是白等,因为他们观察到了页面在发生「进展」,这就是细粒度变化的好处。手机系统动画做得好,会让人觉得系统更流畅,也是这个道理(不信你把系统动画关了试试)。

终极骨架屏

至此,我们的骨架屏看起来已经很完美了,但,还不是最好。

骨架屏本身是一个过渡状态,是为了掩盖加载的时延。而且由于加载一个骨架本身就非常快,这会让一些性能指标非常好看,例如 FP(首次渲染),FCP(首次内容渲染)。但是用户看到骨架屏并不意味着页面加载完成,反而是「加载刚刚开始」。骨架屏不仅会麻痹用户也会麻痹开发,让他们忽略真正需要做的优化:优化接口响应速度,优化资源加载速度,优化程序运行效率,这些实实在在的优化将会使骨架屏的展示时间越来越短。

因此在上了骨架屏之后,并非就万事大吉了,骨架屏的终极目标应当是:「让骨架屏消失」。

真的能实现吗?无论怎么优化,毕竟还有网络、客户机性能等无法掌控的客观因素,让骨架屏真正消失是不现实的。那有没有办法让用户「感觉」骨架屏已经消失了呢?

一个案例:

这里看一个货拉拉小程序的例子,事先声明我并没有提前打开小程序放在后台,是真实的从头启动,且动图做了放慢处理:

请注意,在小程序弹出的一瞬间,就已经是一个「富内容」的界面,看起来就和真实页面一样。如顶部的业务导航,以及车型信息,通常是需要请求接口才能获取到的,是如何做到秒开的呢?

其实你看到的仍然是一个骨架屏,只不过是一个「富内容」的骨架屏。我承认这里用了一些数据缓存、首屏预渲染,甚至依赖了一些微信平台特有的能力,但这不是重点------即使不是微信平台,也有类似的办法------重点是骨架屏可以做到「以假乱真」。

用户「看到」界面,并不意味着他马上就要进行交互。骨架屏本质上是一个「视觉方案」,页面出现的越快,丰富度越高,用户就越满意,至于显示的内容是不是缓存,是否可交互,并没太大所谓,因为用户真正开始下手操作,还要等到几秒以后。

当然这种方案也要根据实际业务酌情考虑,如果缓存的内容和稍后要显示的真实内容差距过大,可能体验并不会很好(这里吐槽一下小红书和知乎,打开APP第一眼看到了想看的内容,然后瞬间给刷没了)。

骨架屏与量化指标

骨架屏往往被当成一种性能优化的方式,因此很多人希望通过量化指标来衡量它所带来的性能提升。但这里其实混淆了概念,骨架屏本身并不会提升性能,它属于一种「体验优化」。

它确实会提升首屏渲染时间,即 FP、FCP 指标。在有些性能统计报告里,FP、FCP 被当成重要的指标,这造成了一些误导。

例如在微信小程序的官方文档里,对「启动完成」的定义是:

在完成视图层代码注入,并收到逻辑层发送的初始数据后,结合从初始数据和视图层得到的页面结构和样式信息,小程序框架会进行小程序首页的渲染,展示小程序首屏,并触发首页的 Page.onReady 事件。

......

小程序框架层面,以 Page.onReady 事件触发标志小程序启动过程完成

小程序启动流程介绍

可见,小程序把「页面初次渲染」当成了「启动完成」的标志。

众所周知,小程序分为「视图层」和「逻辑层」,而根据微信小程序关于启动流程的解释,视图层的初次渲染依赖逻辑层发送的初始数据。假如我们利用一个变量来控制骨架屏的展示,例如skeletonVisible,一般初始值就是 true,视图层在收到这个初始数据之后便会渲染骨架屏,然后触发 onReady,也就是触发了「启动完成」。

凡是通过这种方式使用骨架屏的小程序,在后台看到的所谓「总启动耗时」都会非常好看,让人误以为小程序已经完全加载好,可以给用户使用了,而实际上它只是在记录骨架屏出现的时间。

微信之所以这么定义「启动完成」,应该这么理解:小程序的启动过程包括加载元数据、创建运行环境、下载代码包、代码包注入、应用逻辑初始化、首次路由等等一长串步骤,等到页面初始数据准备好,开始渲染视图的时候,可以认为「一切都已就绪,可以开始表演了」,至于首屏之后的事,那是你们自己写的代码逻辑,我们无法统计。

那么应该用什么指标来衡量真正的「启动完成」呢?

一个常用的指标是 LCP:

LCP(Largest Contentful Paint)最大内容绘制,在可视区域内,页面上的最大内容元素(如图片、视频或文本块)开始出现在屏幕上时触发。该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录

页面已经渲染了最多的内容,可以算作完成了吧。但是考虑这种情况,页面中有大量广告,一般是在主内容加载完成后才出现,而显然我们不用等广告全出现,就可以开始使用了。这时,LCP 时间会比我们实际期望的要滞后。

既然如此可以用TTI?

TTI(Time To Interactive:页面交互时间), 用于衡量网页实现完全可交互所需的时间。在以下情况下,网页会被视为完全互动网页:

  • 网页会显示有用内容,内容的衡量依据是 FCP
  • 大多数可见页面元素都会注册事件处理脚本,并且
  • 网页会在 50 毫秒内响应用户互动。

但是如何定义页面可以开始交互了呢?哪怕页面上只出现了一个按钮,其他部分还在加载中------例如渐进式的骨架屏------也可以认为是可交互。 实际上 lighthouse 对 TTI 的计算方式非常苛刻和复杂,而且因为普适性不高,已从最新的标准中移除了。

在货拉拉小程序的实践中,我们创造了一个指标来衡量加载性能:当渲染首屏所需的数据都已准备好时,标记为「加载已完成」。这个指标是根据实际业务逻辑,在代码中手工记录的。你可能会说这个指标只考虑数据没考虑渲染,实际页面渲染完成的时间肯定要比这个时间要长。的确,严格意义上需要等页面渲染完成才能算「启动完成」。但我们记录这个指标的目的,是为了以此为参照进行性能优化,而性能优化的最主要的目标就是「尽快把数据准备好」。渲染耗时相对比较独立,可以另外专门优化。

所以,不要盲目的迷恋官方指标,而是要理解其具体的含义。指标的制定,需要结合实际需求,「什么都信官方的只会害了你」。

总结

骨架屏的诞生是为了提升用户体验,而由于开发成本较低,在业务中容易被滥用,反而影响了实际的体验。本文通过回顾骨架屏演变历史和一系列案例,想要告诉读者的是:骨架屏是一种「手段」,而「体验」才是其核心目的。要做好骨架屏,应当从「提升用户体验」出发,即时的反馈进展,细粒度、平滑的过渡,让加载的过程不突兀,不枯燥。这样你就会发现,骨架屏不是孤立的、一成不变的,它可以有更多样的表现形式,可以和其他优化点结合实现更好的效果。只要掌握了其核心的原则,想做出一个好的骨架屏就不是什么难事了。

相关推荐
JINGWHALE135 分钟前
设计模式 行为型 命令模式(Command Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·命令模式
$程37 分钟前
【React】漫游式引导
前端·javascript·react.js
请叫我飞哥@38 分钟前
HTML5 波动动画(Pulse Animation)详解
前端·html·html5
凯哥爱吃皮皮虾39 分钟前
前端测试框架Jest基础入门
前端·javascript·jest
山猪打不过家猪39 分钟前
React(二)——Admin主页/Orders页面/Category页面
前端·javascript·react.js
Hellc00741 分钟前
新手入门 React .tsx 项目:从零到实战
前端·react.js·前端框架
&白帝&1 小时前
HTML5 WebSocket
前端·websocket·html5
2401_897579652 小时前
AI赋能房地产:利用AI前端技术提升VR/AR浏览体验
前端·人工智能·vr
小馋喵知识杂货铺2 小时前
XPath语法详解及案例讲解
java·前端·javascript
komo莫莫da2 小时前
第5章——与HTTP协作的Web服务器
服务器·前端·http