Skeleton 骨架屏
在数据加载过程中提供占位与动效,改善等待体验。
基础用法
最简单的骨架占位。
<template>
<u-skeleton />
<br />
<u-skeleton style="--u-skeleton-circle-size: 100px">
<template #template>
<u-skeleton-item variant="circle" />
</template>
</u-skeleton>
</template>
可配置行数
可自行配置占位行数。为更接近真实排版,实际渲染行数会比传入值多 1 行(额外渲染一行约占 33% 宽度的「标题」占位)。
<template>
<u-skeleton :rows="5" />
</template>
动画
通过 animated 控制是否显示加载动画;为 true 时,u-skeleton 子节点会播放闪烁动画。
<template>
<u-skeleton :rows="5" animated />
</template>
自定义模板
组件仅提供常见占位结构;可通过 template 插槽完全自定义。构建自定义结构时请尽量贴近真实 DOM,以减少高度跳变导致的布局抖动。
<template>
<u-skeleton style="width: 240px">
<template #template>
<u-skeleton-item variant="image" style="width: 240px; height: 240px" />
<div style="padding: 14px">
<u-skeleton-item variant="p" style="width: 50%" />
<div
style="
display: flex;
align-items: center;
justify-items: space-between;
"
>
<u-skeleton-item variant="text" style="margin-right: 16px" />
<u-skeleton-item variant="text" style="width: 30%" />
</div>
</div>
</template>
</u-skeleton>
</template>
加载状态
加载结束后需要展示真实内容时,可用 loading 控制是否显示骨架;真实内容放在默认插槽中。
<template>
<u-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px">Switch Loading</label>
<u-switch v-model="loading" />
</div>
<u-skeleton style="width: 240px" :loading="loading" animated>
<template #template>
<u-skeleton-item variant="image" style="width: 240px; height: 240px" />
<div style="padding: 14px">
<u-skeleton-item variant="h3" style="width: 50%" />
<div
style="
display: flex;
align-items: center;
justify-items: space-between;
margin-top: 16px;
height: 16px;
"
>
<u-skeleton-item variant="text" style="margin-right: 16px" />
<u-skeleton-item variant="text" style="width: 30%" />
</div>
</div>
</template>
<template #default>
<u-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px">
<span>Delicious hamburger</span>
<div class="bottom card-header">
<div class="time">{{ currentDate }}</div>
<u-button text class="button">Operation button</u-button>
</div>
</div>
</u-card>
</template>
</u-skeleton>
</u-space>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const loading = ref(true)
const currentDate = new Date().toDateString()
</script>
列表占位
列表数据尚未返回时,可用 count 控制渲染多少个骨架模板。不建议渲染过多假节点,会影响性能且销毁耗时更长,请将 count 控制在必要范围内。
TIP
不推荐在浏览器中渲染大量假 UI,仍可能造成性能问题并延长骨架销毁时间。count 尽量保持较小以获得更好体验。
<template>
<u-space style="width: 100%" fill>
<div>
<u-button @click="setLoading">Click me to reload</u-button>
</div>
<u-skeleton
style="display: flex; gap: 8px"
:loading="loading"
animated
:count="3"
>
<template #template>
<div style="flex: 1">
<u-skeleton-item variant="image" style="height: 240px" />
<div style="padding: 14px">
<u-skeleton-item variant="h3" style="width: 50%" />
<div
style="
display: flex;
align-items: center;
justify-items: space-between;
margin-top: 16px;
height: 16px;
"
>
<u-skeleton-item variant="text" style="margin-right: 16px" />
<u-skeleton-item variant="text" style="width: 30%" />
</div>
</div>
</div>
</template>
<template #default>
<u-card
v-for="item in lists"
:key="item.name"
:body-style="{ padding: '0px', marginBottom: '1px' }"
>
<img
:src="item.imgUrl"
class="image multi-content"
style="max-width: 100%"
/>
<div style="padding: 14px">
<span>{{ item.name }}</span>
<div class="bottom card-header">
<div class="time">{{ currentDate }}</div>
<u-button text class="button">Operation button</u-button>
</div>
</div>
</u-card>
</template>
</u-skeleton>
</u-space>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
interface ListItem {
imgUrl: string
name: string
}
const loading = ref(true)
const lists = ref<ListItem[]>([])
const currentDate = new Date().toDateString()
const setLoading = () => {
loading.value = true
setTimeout(() => {
loading.value = false
}, 2000)
}
onMounted(() => {
loading.value = false
lists.value = [
{
imgUrl:
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
name: 'Deer',
},
{
imgUrl:
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
name: 'Horse',
},
{
imgUrl:
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
name: 'Mountain Lion',
},
]
})
</script>
避免闪烁
接口响应极快时,骨架刚出现又立刻切换为真实内容,容易产生闪烁。可使用 throttle 做展示/隐藏的节流。
TIP
throttle 支持 number 与 object:传数字等价于 { leading: xxx },控制骨架出现节流;也可传 { trailing: xxx } 控制消失节流。

<template>
<u-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px">Switch Loading</label>
<u-switch v-model="loading" />
</div>
<u-skeleton
style="width: 240px"
:loading="loading"
animated
:throttle="500"
>
<template #template>
<u-skeleton-item variant="image" style="width: 240px; height: 265px" />
<div style="padding: 14px">
<u-skeleton-item variant="h3" style="width: 50%" />
<div
style="
display: flex;
align-items: center;
justify-items: space-between;
margin-top: 16px;
height: 16px;
"
>
<u-skeleton-item variant="text" style="margin-right: 16px" />
<u-skeleton-item variant="text" style="width: 30%" />
</div>
</div>
</template>
<template #default>
<u-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px">
<span>Delicious hamburger</span>
<div class="bottom card-header">
<div class="time">{{ currentDate }}</div>
<u-button text class="button">operation button</u-button>
</div>
</div>
</u-card>
</template>
</u-skeleton>
</u-space>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const loading = ref(false)
const currentDate = new Date().toDateString()
</script>
初次加载
当 loading 初始即为 true 时,可设置 throttle: { initVal: true, leading: xxx },使首屏骨架立即展示而不被 leading 节流挡住。
<template>
<u-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px">Switch Loading</label>
<u-switch v-model="loading" />
</div>
<u-skeleton
style="width: 240px"
:loading="loading"
animated
:throttle="{ leading: 500, initVal: true }"
>
<template #template>
<u-skeleton-item variant="image" style="width: 240px; height: 265px" />
<div style="padding: 14px">
<u-skeleton-item variant="h3" style="width: 50%" />
<div
style="
display: flex;
align-items: center;
justify-items: space-between;
margin-top: 16px;
height: 16px;
"
>
<u-skeleton-item variant="text" style="margin-right: 16px" />
<u-skeleton-item variant="text" style="width: 30%" />
</div>
</div>
</template>
<template #default>
<u-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px">
<span>Delicious hamburger</span>
<div class="bottom card-header">
<div class="time">{{ currentDate }}</div>
<u-button text class="button">operation button</u-button>
</div>
</div>
</u-card>
</template>
</u-skeleton>
</u-space>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const loading = ref(true)
const currentDate = new Date().toDateString()
</script>
切换显隐时减少跳动
TIP
可设置 throttle: { initVal: true, leading: xxx, trailing: xxx } 控制骨架首次展示,并在 loading 切换时让过渡更平滑。
在业务组件随加载状态显隐切换时,可设置 throttle: { leading: xxx, trailing: xxx } 减轻布局跳动。
<template>
<u-space direction="vertical" alignment="flex-start">
<div>
<label style="margin-right: 16px">Switch Loading</label>
<u-switch v-model="loading" />
</div>
<u-skeleton
style="width: 240px"
:loading="loading"
animated
:throttle="{ leading: 500, trailing: 500, initVal: true }"
>
<template #template>
<u-skeleton-item variant="image" style="width: 240px; height: 265px" />
<div style="padding: 14px">
<u-skeleton-item variant="h3" style="width: 50%" />
<div
style="
display: flex;
align-items: center;
justify-items: space-between;
margin-top: 16px;
height: 16px;
"
>
<u-skeleton-item variant="text" style="margin-right: 16px" />
<u-skeleton-item variant="text" style="width: 30%" />
</div>
</div>
</template>
<template #default>
<u-card :body-style="{ padding: '0px', marginBottom: '1px' }">
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
class="image"
/>
<div style="padding: 14px">
<span>Delicious hamburger</span>
<div class="bottom card-header">
<div class="time">{{ currentDate }}</div>
<u-button text class="button">operation button</u-button>
</div>
</div>
</u-card>
</template>
</u-skeleton>
</u-space>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const loading = ref(false)
const currentDate = new Date().toDateString()
</script>
骨架屏 API
骨架屏 属性
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| animated | 是否显示动画 | boolean | false |
| count | 渲染多少个骨架模板 | number | 1 |
| loading | 为 true 时显示骨架,为 false 时显示默认插槽真实内容 | boolean | false |
| rows | 占位行数;未提供 template 插槽时生效 | number | 3 |
| throttle | 延迟毫秒;数字表示延迟显示;也可传 { leading, trailing } 控制显隐节流;需控制 loading 初始行为时可传 { initVal: true } | number / object | 0 |
骨架屏 插槽
| 名称 | 说明 | 类型 |
|---|---|---|
| default | 加载完成后的真实内容 | object |
| template | 骨架模板内容 | object |
骨架项 API
骨架项 属性
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| variant | 骨架元素类型 | enum | text |