ant-design-vue Skeleton 骨架屏组件完全指南:API 详解与源码级实现原理
【免费下载链接】ant-design-vue🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜项目地址: https://gitcode.com/gh_mirrors/an/ant-design-vue
Skeleton(骨架屏)是 ant-design-vue 中用于「在内容加载完成前展示占位图形」的反馈型组件,能显著缓解用户等待焦虑、降低页面跳动感。本文将围绕 components/skeleton/index.zh-CN.md 官方文档的完整 API 体系展开,并结合仓库源码(Skeleton.tsx、style/index.ts 等)剖析其组合逻辑、默认布局策略、动画实现与样式 Token 机制,帮助你在真实项目中正确选型、精准配置并理解其底层运作方式。
一、组件定位:什么时候该用 Skeleton
根据官方文档,Skeleton 的核心使用场景有四类:
- 网络较慢、需要长时间等待加载处理的场景,避免页面长时间空白;
- 图文信息内容较多的列表 / 卡片,用骨架占位勾勒出即将出现的内容轮廓;
- 仅在第一次加载数据时使用,数据就绪后切换为真实内容;
- 它可以被 Spin 完全替代,但在可用场景下能提供比 Spin 更好的视觉效果和用户体验——Spin 只是一个居中的加载图标,而 Skeleton 通过模拟标题、段落、头像的真实排版结构,让用户提前感知页面布局,减少加载完成时的视觉跳动。
从组件分类看,Skeleton 属于「反馈(Feedback)」类型组件(见文档 frontmatter 中的type: 反馈),与 Spin、Message 等并列,定位是「在需要等待加载内容的位置提供一个占位图形组合」。
二、Skeleton 主组件 API 详解
Skeleton 主组件接收 5 个核心属性,官方文档参数表如下:
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| active | 是否展示动画效果 | boolean | false |
| avatar | 是否显示头像占位图 | boolean | SkeletonAvatarProps | false |
| loading | 为true时,显示占位图。反之则直接展示子组件 | boolean | - |
| paragraph | 是否显示段落占位图 | boolean | SkeletonParagraphProps | true |
| title | 是否显示标题占位图 | boolean | SkeletonTitleProps | true |
2.1 基本用法与默认组合
最简单的用法只需一行代码(见 demo/basic.vue):
<a-skeleton />不传任何参数时,会渲染出「标题 + 段落」的默认骨架组合。这一行为由 Skeleton.tsx 中的initDefaultProps决定:
props: initDefaultProps(skeletonProps(), { avatar: false, title: true, paragraph: true, }),即默认avatar=false、title=true、paragraph=true,与文档默认值完全一致。
2.2 avatar / title / paragraph:布尔值还是对象
这三个属性既可以是boolean,也可以是各自对应的配置对象。当传入对象时,其内部的配置项会合并到组件自动计算的基础属性之上。核心逻辑见 Skeleton.tsx:
function getComponentProps<T>(prop: T | boolean | undefined): T | {} { if (prop && typeof prop === 'object') { return prop; } return {}; }getComponentProps负责抽取对象形式的配置;当传入布尔值时返回空对象,则完全采用组件内部的默认布局。
2.3 智能默认布局:根据组合自动推算占位尺寸
Skeleton 并不是简单地堆砌三个占位块,而是会根据「当前显示了哪些部分」自动调整每个部分的默认尺寸,让组合看起来更接近真实排版。这套逻辑在 Skeleton.tsx 中实现:
function getAvatarBasicProps(hasTitle: boolean, hasParagraph: boolean): SkeletonAvatarProps { if (hasTitle && !hasParagraph) { // 只有标题时,头像用方形 return { size: 'large', shape: 'square' }; } return { size: 'large', shape: 'circle' }; } function getTitleBasicProps(hasAvatar: boolean, hasParagraph: boolean): SkeletonTitleProps { if (!hasAvatar && hasParagraph) { return { width: '38%' }; } if (hasAvatar && hasParagraph) { return { width: '50%' }; } return {}; } function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): SkeletonParagraphProps { const basicProps: SkeletonParagraphProps = {}; if (!hasAvatar || !hasTitle) { basicProps.width = '61%'; } if (!hasAvatar && hasTitle) { basicProps.rows = 3; } else { basicProps.rows = 2; } return basicProps; }可以总结出以下默认策略:
- 头像(avatar):默认
size: 'large';当「有标题但无段落」时自动变为square(方形),其余情况为circle(圆形); - 标题(title):有段落无头像时宽度为
38%,有头像且有段落时宽度为50%,否则撑满 100%; - 段落(paragraph):无头像或无标题时宽度为
61%(只作用于最后一行);无头像但有标题时默认 3 行,其余情况默认 2 行。
因此,像官方演示中的「复杂组合」——<a-skeleton avatar :paragraph="{ rows: 4 }" />(见 demo/complex.vue)——只需覆盖paragraph.rows,头像与标题的尺寸、宽度仍由组件自动推导。
2.4 loading:占位与真实内容的一键切换
loading是 Skeleton 与数据流结合的关键开关。为true时渲染占位图;为false时直接渲染slots.default中的真实内容。该分支逻辑见 Skeleton.tsx:
if (loading || props.loading === undefined) { // ... 渲染骨架占位图 } return slots.default?.();注意一个细节:props.loading === undefined时同样渲染占位图,即未显式传入 loading 时,组件默认处于骨架态,直到你传入loading=false才展示子组件。官方演示 demo/children.vue 给出了典型用法——加载中展示骨架,加载完成后无缝切换为真实内容:
<a-skeleton :loading="loading"> <div> <h4>Ant Design Vue, a design language</h4> <p> We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently. </p> </div> </a-skeleton> <a-button :disabled="loading" @click="showSkeleton">Show Skeleton</a-button>const loading = ref<boolean>(false); const showSkeleton = () => { loading.value = true; setTimeout(() => { loading.value = false; }, 3000); };结合a-list使用时的完整示例可参考 demo/list.vue,在a-list-item内部包裹<a-skeleton :loading="loading" active avatar>,实现列表骨架加载。
2.5 active:加载光效动画
active为true时,骨架块会呈现从左到右的渐变扫光动画。该动画并非独立的 JS 逻辑,而是纯 CSS 实现:由 style/index.ts 定义的Keyframes驱动:
const skeletonClsLoading = new Keyframes(`ant-skeleton-loading`, { '0%': { transform: 'translateX(-37.5%)' }, '100%': { transform: 'translateX(37.5%)' }, });动画时长、渐变色带等由设计 Token 控制(style/index.ts):
skeletonLoadingBackground: `linear-gradient(90deg, ${token.color} 25%, ${token.colorGradientEnd} 37%, ${token.color} 63%)`, skeletonLoadingMotionDuration: '1.4s',即背景是一条 90 度方向的渐变(基础色 25% → 渐变末端色 37% → 基础色 63%),通过::after伪元素在 1.4 秒内往返平移形成扫光效果(style/index.ts)。官方演示见 demo/active.vue:<a-skeleton active />。
三、组合子组件:SkeletonAvatar / SkeletonTitle / SkeletonParagraph
主组件内部将「头像 / 标题 / 段落」拆分为三个独立组件,位于 components/skeleton/ 目录下:
- Avatar.tsx:渲染头像占位,接收
size与shape; - Title.tsx:渲染标题占位,接收
width; - Paragraph.tsx:渲染段落占位,接收
rows与width。
3.1 SkeletonAvatarProps
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| shape | 指定头像的形状 | circle|square | - |
| size | 设置头像占位图的大小 | number |large|small|default | - |
从源码看,Avatar.tsx 内部对未传值的情况做了兜底:size默认'default'、shape默认'circle'。size传入数字时,会被转换为像素尺寸作用于宽高,逻辑见 Element.tsx:
const sizeStyle: CSSProperties = typeof size === 'number' ? { width: `${size}px`, height: `${size}px`, lineHeight: `${size}px`, } : {};3.2 SkeletonTitleProps
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| width | 设置标题占位图的宽度 | number | string | - |
Title.tsx 的实现非常简洁:数字宽度会被拼接为px单位,字符串则原样作为 CSS 宽度:
const zWidth = typeof width === 'number' ? `${width}px` : width; return <h3 class={prefixCls} style={{ width: zWidth }} />;因此width既可以是100(渲染为100px),也可以是'50%'这类百分比字符串。
3.3 SkeletonParagraphProps
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| rows | 设置段落占位图的行数 | number | - |
| width | 设置段落占位图的宽度,若为数组时则为对应的每行宽度,反之则是最后一行的宽度 | number | string | Array<number | string> | - |
width的两种形态对应两种语义,见 Paragraph.tsx:
const getWidth = (index: number) => { const { width, rows = 2 } = props; if (Array.isArray(width)) { return width[index]; // 数组:逐行指定宽度 } // 非数组:仅作用于最后一行 if (rows - 1 === index) { return width; } return undefined; };- 传入数组(如
['40%', '60%', '80%']):数组下标与行号一一对应,每一行使用对应宽度; - 传入单个值(number 或 string):只作用于最后一行,其余行撑满容器宽度。
注意rows默认值为 2(源码const { width, rows = 2 } = props),与前面主组件默认段落行数策略一致。
四、独立占位元素:SkeletonButton / SkeletonInput / SkeletonImage(3.0+)
从 3.0 版本起,Skeleton 还提供了三种可直接独立使用的占位元素,可通过Skeleton.Button、Skeleton.Input、Skeleton.Image或全局注册后的a-skeleton-button等标签使用(注册逻辑见 index.tsx)。
4.1 SkeletonButtonProps(3.0+)
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| active | 是否展示动画效果 | boolean | false | |
| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | |
| shape | 指定按钮的形状 | circle|round|default | - | |
| size | 设置按钮的大小 | large|small|default | - |
Button.tsx 内部将size默认值设为'default'。block为true时,按钮占位宽度扩展为父容器 100%,对应样式见 style/index.ts 中&-block规则。
4.2 SkeletonInputProps(3.0+)
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| active | 是否展示动画效果 | boolean | false |
| size | 设置输入框的大小 | large|small|default | - |
Input.tsx 通过omit(skeletonElementProps(), ['shape'])剔除了输入框不适用的shape属性,同时支持block(源码类型定义SkeletonInputProps中包含block?: boolean,demo 中也演示了<a-skeleton-input style="width: 200px" />这种覆盖宽度写法)。
4.3 SkeletonImage(3.0+)
虽然官方 API 表未单独列出,但 Image.tsx 提供了图片占位:渲染一个内嵌的 SVG 图片图标(viewBox="0 0 1098 1024"),SkeletonImageProps剔除了size、shape、active三个属性。官方演示 demo/element.vue 中展示了按钮、头像、输入框、图像的完整组合及 Active / Block / Size / Shape 的联动控制。
4.4 尺寸与形状的底层统一处理
所有占位元素最终都收敛到 Element.tsx 这个函数式组件。它统一处理三类维度:
- 尺寸:
large/small映射为-lg/-sm类名,数字映射为内联像素宽高; - 形状:
circle/square/round映射为对应形状类名; - 颜色与圆角:由 style/index.ts 中的
genSkeletonElementButton等规则控制,尺寸分别对应设计 Token 的controlHeight/controlHeightLG/controlHeightSM(即 default / large / small 三档),按钮宽度为controlHeight * 2、输入框宽度为controlHeight * 5。
五、样式定制:Token 与 ConfigProvider 联动
Skeleton 的样式通过 style/index.ts 中的genComponentStyleHook('Skeleton', ...)接入 ant-design-vue 的主题系统。它对外暴露两个ComponentToken(style/index.ts):
export type ComponentToken = { color: string; // 骨架块的基础填充色 colorGradientEnd: string; // 动画渐变末端色 };其默认值由主题 Token 映射而来(style/index.ts):
token => { const { colorFillContent, colorFill } = token; return { color: colorFillContent, colorGradientEnd: colorFill, }; }即基础色使用colorFillContent、渐变末端色使用colorFill,这两个 Token 会随主题明暗模式自动切换。因此你可以通过全局主题配置覆盖Skeleton的color与colorGradientEnd来定制骨架颜色。此外,Skeleton 内部还通过useConfigInject('skeleton', props)(见 Skeleton.tsx)接入 ConfigProvider,继承prefixCls与direction,支持全局组件前缀修改与 RTL 布局(-rtl类名见 Skeleton.tsx)。同时round属性可将标题、段落圆角切换为胶囊形状(borderRadius: 100,见 style/index.ts)。
六、与 Spin 的选型对比
官方文档明确指出「可以被 Spin 完全代替,但是在可用的场景下可以比 Spin 提供更好的视觉效果和用户体验」。实际选型建议:
- 内容区域为整块加载、无需预先呈现布局轮廓时,使用
Spin即可; - 列表、卡片、详情页等有固定排版结构的区域,优先使用
Skeleton:它按真实布局渲染占位,内容加载完成切换时页面不会大幅跳动; - 两者也可以组合使用:在骨架区域内的操作按钮上叠加
Spin,兼顾「布局预告」与「局部加载反馈」。
七、快速上手总结
| 需求 | 推荐写法 |
|---|---|
| 最简单占位(标题 + 段落) | <a-skeleton /> |
| 带扫光动画 | <a-skeleton active /> |
| 头像 + 多行段落 | <a-skeleton avatar :paragraph="{ rows: 4 }" />(见 demo/complex.vue) |
| 数据加载切换 | <a-skeleton :loading="loading">真实内容</a-skeleton>(见 demo/children.vue) |
| 列表骨架 | <a-skeleton :loading="loading" active avatar>包裹a-list-item(见 demo/list.vue) |
| 按钮 / 输入框占位 | <a-skeleton-button :active="active" />、<a-skeleton-input :size="size" />(见 demo/element.vue) |
掌握以上 API 与底层实现后,你可以在 ant-design-vue 项目中精准选用 Skeleton 的各类组合与独立元素,并通过 Token 定制与 ConfigProvider 联动让骨架屏与你的设计系统保持一致。如需进一步研究源码,可重点阅读 Skeleton.tsx(组合与默认布局策略)、Element.tsx(尺寸/形状统一处理)与 style/index.ts(Token 与动画实现)。
【免费下载链接】ant-design-vue🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜项目地址: https://gitcode.com/gh_mirrors/an/ant-design-vue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考