EN
展现形式 · 概念

Skeleton Loader:骨架屏与加载占位

说明 Skeleton Loader 骨架屏如何在内容到达前用与最终布局相近的灰色占位保持版面稳定,依据 web.dev 的 CLS 优化建议、MDN aria-busy 与 prefers-reduced-motion 讨论布局位移、可访问性、闪烁动画,并强调加载失败必须切换到错误或空状态、慢接口要有超时处理。

Skeleton Loader 是内容尚未到达时,用与最终布局近似的灰色块、线条和圆形占位,先把版面撑起来的加载表现。它回答“这里将有内容,正在取”,不回答进度多少;那是 Progress Bar 的事,也不同于 Empty State,后者是结果已经到达而且为空。

适合与不适合

适合等待时间不长、结构可预期的内容块:评价列表、推荐商品、搜索与筛选结果、异步加载的媒体区。不适合:整个页面(服务端已经能输出的内容就直接输出,不用骨架);结构无法预期的内容;价格、库存、可售状态等交易关键信息用灰块冒充“已有值”;任何拿它掩盖慢接口而没有超时与失败处理的场景。

与相邻模式的区别

相邻模式判据说明
Progress Bar表达结构还是比例骨架预示内容的形状;进度条表达已完成多少。等待时间长且有真实进度时用进度条,不确定又较短时用骨架
Empty State结果是否已到达骨架表示未到,会变化;空状态表示已到且为空。骨架期间不得显示“暂无”,加载失败也不得显示成“暂无”
转圈加载图标是否预留版面转圈只表示忙碌,不预留形状;骨架预留版面,更利于避免位移,但要求结构可预期
Scroll Reveal内容是否已存在滚动显现是内容已在页面里、随滚动出现;骨架是内容还没取到

优点与缺点

优点缺点
预留空间,加载完成时减少布局位移骨架与最终尺寸不一致时,替换会造成位移,等于白做
顾客能预判内容结构长时间停留会让人怀疑页面卡死
比空白或转圈更稳定shimmer 闪烁动画对部分读者不适
可分区加载,部分内容先出现结构可变的内容需要多套骨架,维护成本高

内容与交互要求

尺寸与位移。 web.dev 的 Optimize CLS 页对动态内容的建议是:提前在视口里预留足够空间,例如使用占位符或骨架界面,这样内容加载时不会让页面其他内容意外位移;页面还建议用 min-height、aspect-ratio 和图片的 width/height 预留空间。骨架块的高度与最终内容的差异越大,替换时的位移越大。CLS 页说明其阈值:0.1 及以下为好,超过 0.25 为差,取第 75 百分位;用户点击等离散输入后 500 毫秒内的位移不计入,但骨架替换通常与点击无关,不应指望这条豁免。

已有内容的更新不退回骨架。 筛选、翻页或刷新时,保留旧结果并标记忙碌,比整块换回骨架更稳定,参见集合浏览与筛选与站内搜索。骨架用于首次到达前。

加载失败必须切换。 骨架不能永远停着。请求失败、超时或返回空结果时,必须切换到明确的错误状态(说明未完成并给出重试,见 Empty State 的三类原因)或空状态,并把加载标记撤掉。价格、库存等数据未取得时,不显示旧值冒充最新,与评价展示的处理一致:加载失败隐藏评分与列表,购买区不受影响。

超时。 每个骨架都要有超时上限与兜底状态。超时时长没有标准值,取决于该内容的重要性与接口表现,由产品与后端共同确定,并在超时后停止动画。不要用骨架掩盖慢接口:先量出接口耗时,慢到需要长时间骨架时,优化接口或改为分批加载,而不是加大骨架的存在感。

响应式与无障碍

忙碌状态。 MDN 说明 aria-busy 是全局 ARIA 状态,表示元素正在被修改,变化尚未完成,辅助技术可以等一等再向用户报告更新;true 表示正在更新,默认 false。对需要多部分加载完才通报的 live region,MDN 的做法是设为 true 直到全部加载完成,再设回 false,避免辅助技术在更新完成前就播报。据此:在将被填充的容器上设 aria-busy="true",内容或错误状态到位后立刻设回 false 或移除。加载失败时也必须撤销,否则这个区域一直被标为“更新未完成”,这是对 MDN 描述的推论,未做读屏软件实测。

状态通知。 MDN 的 live regions 指南说明 aria-live 取值 off、polite、assertive,polite 最常用;live region 必须在内容变化前已存在于初始标记中。若需要让读屏用户知道“正在加载”和“已加载”,用一个事先存在的 polite 区域写入简短文字(如“正在加载评价”“已加载 12 条评价”,虚构示例),不要给它焦点,也不要逐块播报。骨架块本身没有信息,应当是没有文本内容的装饰元素,不生成可朗读文字。

动画与减少动态。 shimmer(光带扫过的渐变动画)属于持续动效。MDN 的 prefers-reduced-motion 页说明该媒体特性用来检测用户是否设置了尽量减少非必要动效,可用它移除、减少或替换动画;reduce 时应改为静态灰块,或低幅度的透明度变化。WCAG 2.2 的 2.2.2 Pause, Stop, Hide(A)要求:自动开始、持续超过五秒并与其他内容并行呈现的移动、闪烁、滚动信息,要有暂停、停止或隐藏的机制,除非它是必要活动的一部分;理解文档的例子说明,不与其他内容并行的预加载动画不需要这样的机制。骨架屏往往与页面其他内容并行出现,是否落入该条没有标准逐条结论;稳妥做法是超时后停止动画,同时尊重 prefers-reduced-motion。2.3.3 Animation from Interactions(AAA)针对由交互触发的动效,不是本条的直接依据。闪光频率避免频闪,具体阈值本文未核对。

状态与退化

  • 零项:结果为空时显示 Empty State,不显示骨架。
  • 一项与多项:骨架数量与预计的项数接近;数量未知时取保守的少量,不铺满整屏。
  • 加载:显示骨架并设 aria-busy,容器尺寸与最终内容一致。
  • 错误与超时:切换到错误状态,撤销 aria-busy 与动画,可重试并保留已成功的区域。
  • 过期:旧请求晚返回时不得覆盖新条件(见各模块),骨架与结果以最新请求为准。
  • 无 JavaScript:骨架若由服务端输出而依赖脚本替换,无脚本时会永远停在骨架。应由脚本在客户端加载时才创建骨架,或提供 <noscript> 内容与普通链接兜底。
  • 减少动态:静态灰块,不做 shimmer。

常承载的内容与模块

评价展示(评论区异步加载时预留同尺寸占位)、交叉销售与追加购买(推荐区预留高度,加载失败时安静隐藏)、站内搜索与集合浏览与筛选的首次结果、变体媒体的媒体区。版式上常与网格搭配。

发布前检查

  • 骨架块尺寸是否与最终内容一致,替换时是否会产生位移,用 Chrome 的性能工具核对过吗?
  • 是否设置了超时,超时与失败后是否切换到错误或空状态,动画与 aria-busy 是否被撤销?
  • 数据未取得时是否没有显示旧值或“暂无”?
  • 忙碌与完成状态是否有事先存在的 status 区域通知,而不是逐块播报?
  • prefers-reduced-motion: reduce 下是否没有 shimmer?
  • 无 JavaScript 下是否不会永远停在骨架?

待继续完善

  • 没有在真实页面测量骨架替换前后的 CLS,也没有对比不同骨架策略。
  • 没有在读屏软件中测试 aria-busy 与状态区域的组合播报。
  • 各接口的超时阈值属于业务判断,尚无本库统一的默认值。