Skip to content

Skeleton 骨架屏 ​

加载期间的过渡占位:组件模式用 loading 一键切换「骨架 ↔ 真实内容」;指令模式 v-skeleton 直接骨架化现有元素,不必另写占位模板。微光扫过动画与 ShLazyImage 的骨架一致,颜色 / 时长可通过 CSS 变量定制。

两种用法怎么选

ShSkeleton 组件 —— 适合"整块区域"的占位(文章、卡片、评论),loading 切换真实内容,结构与 Vant / Element Plus / Ant Design 的 Skeleton 对齐:rows / row-width / avatar / title / count / throttle 一应俱全。 v-skeleton 指令 —— 适合"已有布局"的临时遮罩:默认整块覆盖;.deep 测量真实文本行、图片与有尺寸的空元素,在独立层绘制骨架,真实 DOM 原位保留。

基础用法 ​

loading 为 true 渲染骨架,为 false 渲染默认插槽的真实内容。默认是「标题 + 3 行段落」组合。

vue
<script setup lang="ts">
import { ref } from 'vue'
import { ShSkeleton } from 'sh-design'

const loading = ref(true)
</script>

<template>
  <ShSkeleton :loading="loading">
    <p>异步加载完成后的真实内容……</p>
  </ShSkeleton>
</template>

头像 + 组合 / 行宽 / 圆角 ​

avatar 开启圆形头像;row-width 传数组按行下发宽度(单值则统一应用);round 让文字行变成胶囊。

vue
<ShSkeleton avatar :rows="2" title-width="30%" />
<ShSkeleton :rows="3" :row-width="['100%', '80%', '52%']" round />

单形状变体(variant) ​

只需要一个占位块时用 variant:text / rect / circle / image(image 默认高 200px),height 控制高度(circle 时同时作为宽度)。

vue
<ShSkeleton variant="circle" height="64px" />
<ShSkeleton variant="rect" height="120px" />
<ShSkeleton variant="image" height="120px" />

列表占位(count) ​

count 重复渲染整组骨架,常配合 avatar 做列表项占位。

vue
<ShSkeleton avatar :rows="1" :count="3" />

自定义骨架布局(#template) ​

默认组合不够用时,#template 插槽完全接管骨架的内部结构,块元素加 sh-skeleton__block 类即可获得微光效果。

vue
<ShSkeleton :loading="loading">
  <MyCard />
  <template #template>
    <div class="sh-skeleton__block" style="height: 16px; width: 40%" />
    <div class="sh-skeleton__block" style="height: 12px; width: 70%" />
  </template>
</ShSkeleton>

防闪(throttle) ​

请求很快返回时骨架闪一下反而干扰:throttle 让骨架在 loading 持续超过指定毫秒后才出现(loading 结束立即隐藏)。下面左右两块对比,右边开启了 throttle: 500。

点击按钮发起一次 300ms 的"闪请求":开启防闪后骨架不出现,直接呈现内容。
无 throttle(骨架闪现)
throttle 500ms(不闪)
vue
<ShSkeleton :loading="loading" :rows="2" :throttle="500" />

指令用法(v-skeleton) ​

已有成品布局、只想加载期间打个"占位补丁"时,指令最省事:v-skeleton="loading" 骨架化整个元素——子内容隐藏、原位覆盖微光遮罩,尺寸与布局零偏移。传对象 { animated: false } 可关闭动画。

shuke

前端工程师 / 开源爱好者

Vue 3TypeScriptuni-app
vue
<script setup lang="ts">
import { ref } from 'vue'

const loading = ref(true)
</script>

<template>
  <!-- 布局零偏移:加载完内容原位显现 -->
  <div v-skeleton="loading" class="user-card">
    <img src="./avatar.png" alt="头像" />
    <div>{{ user.name }}</div>
  </div>

  <!-- 对象本身为真值,用 loading && 对象 控制关闭 -->
  <div v-skeleton="loading && { animated: false }">...</div>
</template>

按需引入(未全量 app.use(ShDesign))时手动注册指令:

ts
import { vSkeleton } from 'sh-design'

app.directive('Skeleton', vSkeleton)

指令深度模式(v-skeleton.deep) ​

默认模式盖一整块遮罩;.deep(或对象参数 deep: true)则测量现有内容,在独立绝对定位层绘制。真实节点不复制、不搬移、不包裹:容器继续负责布局、背景、圆角与留白,文本通过 Range 按文本流 / 视觉行合并,图片及有尺寸的空元素按真实盒子生成块。

下面两组使用同一份 KPI 数据:35×35 的真实图片、block 两行文本、grid 两列小注、display: contents 行和嵌套小字号单位。可以一直保持 loading,再改变数据,观察无提示时 -- 的短条、嵌套单位合行及长文案换行;动画按钮原位修改同一个选项对象。

无提示:忠实于真实内容
指标图标
--次累计访问次数
今日--次昨日--次
指标图标
--项完成任务
指标图标
--%有效比率
指标图标
--秒平均耗时
有提示:只调整骨架绘制尺寸
指标图标
--次累计访问次数
今日--次昨日--次
指标图标
--项完成任务
指标图标
--%有效比率
指标图标
--秒平均耗时

可选提示:标注真实节点,不另写占位模板 ​

无提示时位置和宽高来自真实内容,不会把 -- 猜成 74px。需要统一视觉尺寸时,在真实节点上标注正数 px:下面把图片绘制为 40×40,两行文本绘制为 74×18 / 92×14,小注每行绘制为 64×12。这些是调用方的设计选择,不是指令的默认值。

vue
<div v-skeleton.deep="loading" class="kpi-card">
  <div style="display: flex; gap: 14px; align-items: center">
    <img
      src="./metric.svg"
      alt="指标图标"
      style="width: 35px; height: 35px; border-radius: 50%"
      data-sh-skeleton-width="40"
      data-sh-skeleton-height="40"
    />
    <div>
      <span style="display: block" data-sh-skeleton-width="74" data-sh-skeleton-height="18">
        {{ value }}<span style="font-size: var(--sh-font-size-sm)">次</span>
      </span>
      <span style="display: block; margin-top: 6px" data-sh-skeleton-width="92" data-sh-skeleton-height="14">
        累计访问次数
      </span>
    </div>
  </div>
  <div style="display: grid; grid-template-columns: auto 1fr; gap: 8px 12px">
    <span
      v-for="note in notes"
      :key="note.id"
      style="display: contents"
      data-sh-skeleton-group
      data-sh-skeleton-width="64"
      data-sh-skeleton-height="12"
    >
      <span>{{ note.label }}</span>
      <span>{{ note.value }}<span style="font-size: var(--sh-font-size-sm)">次</span></span>
    </span>
  </div>
</div>

生成规则与支持边界

  • 文本按文本流 / 行合并:同一主题、同一行的数值与嵌套单位只画一条,多行仍是多条。flex / grid 包装本身不涂大块;独立子项不会仅因同高就自动合并。
  • display: contents 递归遍历:它没有自身盒子;加 data-sh-skeleton-group 后,同一组的可见后代按视觉行合并(可跨 grid 列),不是把整个多行区域铺成一块。嵌套 group 由最外层分组负责。
  • 尺寸提示只管绘制:文本节点的提示应用于该文本流的每一行;组的提示应用于组内每一视觉行;图片 / 空元素应用于自身块。保持左上角,超出宿主可视区域会裁剪,不修改真实宽高、间距和滚动范围。没有盒子的空节点无法凭提示生成位置。
  • 保留容器外观:背景、边框与圆角不统一清空。图片(以及 video / canvas / iframe / SVG / 表单控件)按外框占位,不解析内部像素;有尺寸的空元素保留外框圆角。display:none、visibility:hidden、opacity:0 的内容不绘制。
  • 只在加载期按需测量:DOM / 文本 / 尺寸变化、图片加载、字体加载、滚动及窗口 resize 经 rAF 合并,先读后写;不是零测量,也不是逐帧轮询。关闭、切换模式和卸载时清理观察器、监听、帧与绘制层,恢复定位和相关属性。
  • 适用常规水平排版与正向二维缩放:宿主必须是可容纳绘制层的普通盒子(如 div),支持其边框和滚动。不支持宿主本身为 display:contents / 多行 inline / 替换元素、旋转 / 倾斜 / 3D、竖排、伪元素生成内容、Shadow DOM 或脱离宿主的浮层;持续 CSS 动画引起的位置变化不会逐帧追踪。复杂特殊布局仍可用 ShSkeleton #template。
  • animated: false 与系统 prefers-reduced-motion: reduce 都关闭骨架微光。对象本身为真值,需使用 loading && options 结束加载。

主题定制 ​

通过 CSS 变量定制骨架外观(ShSkeleton 与 v-skeleton 共用同一套变量):

css
/* 换成暗色主题 */
.your-scope {
  --sh-skeleton-bg: hsl(220 14% 22%);
  --sh-skeleton-highlight: hsl(220 14% 30%);
  --sh-skeleton-duration: 1.8s;
}

深度模式保留实际文本流、媒体或显式 group 来源作用域的 --sh-skeleton-bg / --sh-skeleton-highlight / --sh-skeleton-duration / --sh-radius-sm,包括中间祖先上的局部覆盖与 var(...) 引用;只在绘制块传递必要的已解析 token,不复制整套样式。同主题的嵌套单位仍合行,inline 主题变化时才分段;显式 group 合并后统一使用 group 自身作用域的主题。

删除局部覆盖后会清理绘制块的旧 token,回退到继承主题或默认值。与宿主相同的变量直接继承,不固化宿主快照;祖先(含根节点)的主题属性变化、head 内样式节点更新及系统配色切换会触发合帧重读。上方小注示例仅在 .sh-skeleton-doc__notes 内使用已有 --sh-color-bg-active / --sh-color-border 调整骨架颜色,与浅灰容器背景区分,不修改全局主题。

API ​

Props(ShSkeleton) ​

属性说明类型默认值
loading是否加载中:true 渲染骨架,false 渲染默认插槽内容Booleantrue
rows段落行数(组合模式)Number3
row-width行宽:单值统一应用;数组按行下发;缺省时末行 60%、其余 100%String | String[]-
title是否显示标题行Booleantrue
title-width标题行宽度String'38%'
avatar是否显示圆形头像Booleanfalse
avatar-size头像尺寸(宽高一致)String'40px'
round文字行是否为胶囊圆角Booleanfalse
variant单形状模式:text / rect / circle / image,指定后忽略组合配置String-
height单形状高度(circle 时同时作为宽度)String-
count骨架组重复次数(列表占位)Number1
throttleloading 变 true 后延迟多少毫秒再显示骨架,防快请求闪现;loading 变 false 立即隐藏Number0
animated是否开启微光扫过动画Booleantrue
tag渲染的根元素标签String'div'

Slots(ShSkeleton) ​

插槽名说明
default真实内容,loading: false 时渲染
template完全自定义骨架布局(内部块元素加 sh-skeleton__block 类获得微光效果)

指令(v-skeleton) ​

用法说明
v-skeleton="loading"值为真值时骨架化元素:子内容隐藏,原位覆盖微光遮罩(遮罩尺寸与圆角自动跟随元素自身,圆形元素即圆形遮罩)
v-skeleton.deep="loading"测量真实文本流 / 行、图片与有尺寸的空元素,独立绘制,不改变真实节点布局
v-skeleton="loading && { animated: false, deep: true }"对象参数:animated: false 关闭微光动画,deep: true 开启深度模式;也支持原位修改同一选项对象

真实节点提示(仅 deep) ​

属性值说明
data-sh-skeleton-group属性存在即生效将可见后代按视觉行合并,支持 display: contents;删除属性取消分组
data-sh-skeleton-width正数像素,如 74 / 74px可选绘制宽度;不接受百分比、负数、零或表达式,无效值忽略
data-sh-skeleton-height正数像素,如 18 / 18px可选绘制高度;规则同 width,均不改变真实布局

CSS 变量 ​

变量默认值说明
--sh-skeleton-bg#f2f3f5骨架底色
--sh-skeleton-highlight#e6e8eb微光高亮色
--sh-skeleton-duration1.4s单次扫过时长
--sh-radius-sm4px文字骨架默认圆角;deep 媒体或已有圆角的文本流保留来源外框圆角

Released under the MIT License.