Ant Design Skeleton 组件 active 动画效果全解析:从一行代码到 CSS 动画底层实现
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
导读
Skeleton(骨架屏)是 Ant Design 中最常用的加载占位组件之一,而active属性正是让骨架屏从"静止的灰色块"变成"有呼吸感的加载提示"的关键开关。本文以components/skeleton/demo/active.md演示为切入点,结合仓库源码深入讲解active动画的用法、生效范围、CSS 动画底层实现原理,以及它与loading、round等属性的配合技巧,帮助你写出既专业又流畅的加载体验。
一、active 动画效果:两行文档背后的核心能力
在仓库的演示文档 components/skeleton/demo/active.md 中,对动画效果的描述极为凝练:
- zh-CN:显示动画效果。
- en-US:Display active animation.
与之配套的演示代码位于 components/skeleton/demo/active.tsx,完整代码如下:
import React from 'react'; import { Skeleton } from 'antd'; const App: React.FC = () => <Skeleton active />; export default App;这就是骨架屏动画的"一行代码"用法:在<Skeleton />上添加active布尔属性,占位图形就会呈现来回扫过的"微光"效果,向用户传达"内容正在加载中"的信号。虽然演示代码只有一行,但其背后涉及active属性的传递链路、CSS Keyframes 动画定义、以及主题 Token 配置等多层实现,下面逐一展开。
二、active 属性在哪些组件上生效
active并非只作用于<Skeleton />主组件。从源码结构看,Skeleton 是一个复合组件(Compound Component),通过静态属性挂载了Button、Avatar、Input、Image、Node五个子组件,定义于 components/skeleton/Skeleton.tsx#L188-L192:
Skeleton.Button = SkeletonButton; Skeleton.Avatar = SkeletonAvatar; Skeleton.Input = SkeletonInput; Skeleton.Image = SkeletonImage; Skeleton.Node = SkeletonNode;因此,active动画可以作用于以下全部形态:
| 使用方式 | 说明 |
|---|---|
<Skeleton active /> | 主骨架屏(标题 + 段落,可含头像)整体动画 |
<Skeleton.Button active /> | 按钮形状骨架屏动画 |
<Skeleton.Avatar active /> | 头像骨架屏动画 |
<Skeleton.Input active /> | 输入框骨架屏动画 |
<Skeleton.Image active /> | 图片骨架屏动画 |
<Skeleton.Node active>...</Skeleton.Node> | 自定义节点骨架屏动画 |
主组件:active 类名的拼接
在 components/skeleton/Skeleton.tsx#L163-L176 中,active会被拼接到根节点类名上:
const cls = classNames( prefixCls, { [`${prefixCls}-with-avatar`]: hasAvatar, [`${prefixCls}-active`]: active, [`${prefixCls}-rtl`]: direction === 'rtl', [`${prefixCls}-round`]: round, }, // ... );当active为true时,渲染出的 DOM 根节点会带有ant-skeleton-active类(默认 prefix 为ant),CSS 层据此触发动画。
子组件:active 直接透传
以 components/skeleton/Button.tsx#L29-L40 为例,子组件同样通过类名拼接响应active:
const cls = classNames( prefixCls, `${prefixCls}-element`, { [`${prefixCls}-active`]: active, [`${prefixCls}-block`]: block, }, className, rootClassName, hashId, cssVarCls, );components/skeleton/Avatar.tsx#L28-L38 中的实现与之对称。这些子组件最终都会把尺寸、形状等参数交给统一的底层元素 components/skeleton/Element.tsx 渲染,Element的 props 类型中同样声明了active?: boolean(见 components/skeleton/Element.tsx#L4-L12),并且支持数字类型的size直接生成像素级宽高。
三、动画的底层实现:CSS-in-JS 中的 Keyframes
active动画并非由 JavaScript 逐帧驱动,而是通过@ant-design/cssinjs生成的标准 CSS 动画实现。核心代码位于 components/skeleton/style/index.ts#L44-L51:
const skeletonClsLoading = new Keyframes(`ant-skeleton-loading`, { '0%': { backgroundPosition: '100% 50%', }, '100%': { backgroundPosition: '0 50%', }, });这个名为ant-skeleton-loading的关键帧动画,让背景的backgroundPosition从100% 50%(最右侧)平移到0 50%(最左侧),从而产生"光带从左向右扫过"的视觉效果。
动画应用规则
动画样式通过genSkeletonColor函数统一生成(components/skeleton/style/index.ts#L76-L83):
const genSkeletonColor = (token: SkeletonToken): CSSObject => ({ background: token.skeletonLoadingBackground, backgroundSize: '400% 100%', animationName: skeletonClsLoading, animationDuration: token.skeletonLoadingMotionDuration, animationTimingFunction: 'ease', animationIterationCount: 'infinite', });关键点说明:
backgroundSize: '400% 100%':背景被放大到容器宽度的 4 倍,为渐变光带留出足够的平移空间;animationTimingFunction: 'ease':缓动函数让动画在首尾减速,观感更自然;animationIterationCount: 'infinite':无限循环,直到组件卸载或active被关闭。
动画的触发范围
genBaseStyle中,active动画被限定在带-active类名的容器内,并一次性作用于所有子占位元素(components/skeleton/style/index.ts#L357-L369):
[`${componentCls}${componentCls}-active`]: { [` ${skeletonTitleCls}, ${skeletonParagraphCls} > li, ${skeletonAvatarCls}, ${skeletonButtonCls}, ${skeletonInputCls}, ${skeletonImageCls} `]: { ...genSkeletonColor(token), }, },也就是说,只要在<Skeleton active />上开启动画,内部的标题(-title)、段落每一行(-paragraph > li)、头像(-avatar)、按钮(-button)、输入框(-input)、图片(-image)占位块会同步且统一地播放同一条光带动画,整体视觉上非常整齐。
光带渐变与动画时长来自 Design Token
动画背景的渐变色和时长并不是写死的魔法值,而是由主题 Token 提供(components/skeleton/style/index.ts#L404-L405):
skeletonLoadingBackground: `linear-gradient(90deg, ${token.gradientFromColor} 25%, ${token.gradientToColor} 37%, ${token.gradientFromColor} 63%)`, skeletonLoadingMotionDuration: '1.4s',- 渐变色:90 度线性渐变,从起点色
gradientFromColor(默认取colorFillContent)过渡到终点色gradientToColor(默认取colorFill),再回到起点色,构成"亮-暗-亮"的光带; - 动画时长:
1.4s完成一次完整扫动,节奏舒缓,不会造成视觉压迫。
开发者可以通过主题覆盖gradientFromColor、gradientToColor等 Token 自定义光带配色。需要注意的是,旧的color、colorGradientEnd两个 Token 已被标记为废弃,源码中通过deprecatedTokens声明了从旧到新的映射关系(components/skeleton/style/index.ts#L410-L415)。
四、与 loading 配合:从占位到内容的无缝切换
active动画只有在"占位状态"下才有意义,而控制占位状态的是loading属性。在 components/skeleton/Skeleton.tsx#L108 中:
if (loading || !('loading' in props)) { // 渲染骨架屏 } return children ?? null; // 否则渲染真实内容规则解析:
- 未传
loading属性时,始终渲染骨架屏(这也是<Skeleton active />单独使用即可看到动画的原因); loading为true时渲染骨架屏;loading为false时直接渲染children传入的真实内容,没有子节点则返回null。
仓库测试 components/skeleton/tests/index.test.tsx#L45-L58 对该行为有明确覆盖:loading={false}且子节点为0时,渲染结果文本是0;子节点为[1, 2, 3]时渲染出123。因此,在实际业务中典型的加载模式是:
import React, { useEffect, useState } from 'react'; import { Skeleton, Card } from 'antd'; const App: React.FC = () => { const [loading, setLoading] = useState(true); const [data, setData] = useState<string>(''); useEffect(() => { // 模拟异步请求 setTimeout(() => { setData('加载完成的内容'); setLoading(false); }, 2000); }, []); return ( <Card> <Skeleton active loading={loading} avatar paragraph={{ rows: 4 }}> {data} </Skeleton> </Card> ); }; export default App;数据到达后loading置为false,骨架屏连同动画一并消失,内容直接呈现,全程无需手动控制动画的开关。
五、active 动画相关的完整属性速查
为了更准确地使用动画效果,这里汇总主组件与各子组件中与active相关(以及直接影响动画观感)的完整属性清单(依据 components/skeleton/index.zh-CN.md 的 API 章节整理):
Skeleton(主组件)
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| active | 是否展示动画效果 | boolean | false |
| avatar | 是否显示头像占位图 | boolean | SkeletonAvatarProps | false |
| loading | 为 true 时显示占位图,反之直接展示子组件 | boolean | - |
| paragraph | 是否显示段落占位图 | boolean | SkeletonParagraphProps | true |
| round | 为 true 时,段落和标题显示圆角 | boolean | false |
| title | 是否显示标题占位图 | boolean | SkeletonTitleProps | true |
SkeletonAvatarProps
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| active | 是否展示动画效果,仅在单独使用头像骨架时生效 | boolean | false |
| shape | 指定头像的形状 | circle|square | - |
| size | 设置头像占位图的大小 | number |large|small|default | - |
SkeletonTitleProps
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| width | 设置标题占位图的宽度 | number | string | - |
SkeletonParagraphProps
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| rows | 设置段落占位图的行数 | number | - |
| width | 设置段落占位图的宽度,若为数组则是对应的每行宽度,反之是最后一行的宽度 | number | string | Array<number | string> | - |
SkeletonButtonProps
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| active | 是否展示动画效果 | boolean | false | |
| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | 4.17.0 |
| shape | 指定按钮的形状 | circle|round|square|default | - | |
| size | 设置按钮的大小 | large|small|default | - |
SkeletonInputProps
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| active | 是否展示动画效果 | boolean | false |
| size | 设置输入框的大小 | large|small|default | - |
段落width的"逐行宽度"逻辑可以在 components/skeleton/Paragraph.tsx#L14-L24 中看到具体实现:当width为数组时按索引取对应值;为单个值时仅作用于最后一行(rows - 1 === index)。例如paragraph={{ rows: 3, width: ['80%', '90%', '60%'] }}可构造出前长后短的典型段落效果,配合active动画视觉层次更丰富。
六、组合形态:动画效果在不同骨架元素上的应用
active动画与各类骨架元素组合时,动画始终生效,但占位图形状由各自的shape、size决定。参考 components/skeleton/demo/element.tsx 的交互式演示,可以组合出如下形态:
import React from 'react'; import { Divider, Skeleton, Space, Switch } from 'antd'; import { useState } from 'react'; const App: React.FC = () => { const [active, setActive] = useState(true); return ( <> <Space> <Skeleton.Button active={active} size="large" shape="round" /> <Skeleton.Avatar active={active} size="large" shape="circle" /> <Skeleton.Input active={active} size="large" /> </Space> <Divider /> <Skeleton.Button active={active} block /> <Divider /> <Skeleton active avatar paragraph={{ rows: 4 }} /> <Switch checked={active} onChange={setActive} /> </> ); }; export default App;要点:
Skeleton.Avatar的shape仅支持circle与square,Skeleton.Button额外支持round(胶囊形);- 数字
size(如Skeleton.Avatar size={40})会被 components/skeleton/Element.tsx#L28-L38 直接转为像素宽高与行高; - 动画开启与否与形状、尺寸完全正交,可以自由组合,不会互相干扰。
七、动画相关的主题定制(Design Token)
如果需要让动画光带更贴合品牌视觉,可以通过 ConfigProvider 覆盖 Skeleton 的主题 Token。与动画直接相关的 Token 汇总如下(定义于 components/skeleton/style/index.ts#L7-L42):
| Token | 说明 | 默认推导 |
|---|---|---|
| gradientFromColor | 渐变色起点颜色 | colorFillContent |
| gradientToColor | 渐变色终点颜色 | colorFill |
| titleHeight | 标题骨架屏高度 | controlHeight / 2 |
| blockRadius | 骨架屏圆角 | borderRadiusSM |
| paragraphMarginTop | 段落骨架屏上间距 | marginLG + marginXXS |
| paragraphLiHeight | 段落骨架屏单行高度 | controlHeight / 2 |
光带扫描的时长(skeletonLoadingMotionDuration,默认1.4s)属于内部 Token,如需调整动画节奏,可从全局层面覆盖基础 Token 或直接定制渐变两端的颜色:
import React from 'react'; import { ConfigProvider, Skeleton } from 'antd'; const App: React.FC = () => ( <ConfigProvider theme={{ components: { Skeleton: { gradientFromColor: '#f0f5ff', gradientToColor: '#d6e4ff', }, }, }} > <Skeleton active avatar paragraph={{ rows: 3 }} /> </ConfigProvider> );结语
从 components/skeleton/demo/active.md 中那句"显示动画效果",到 components/skeleton/demo/active.tsx 的一行<Skeleton active />,再到 components/skeleton/style/index.ts 中基于 CSS-in-JS 的ant-skeleton-loading关键帧动画,Ant Design 把"加载中"的视觉反馈封装成了一个几乎零成本的属性开关。理解这条从active属性 →-active类名 → Keyframes 动画 → Design Token 的完整链路,你就能在列表加载、卡片占位、详情页骨架等场景中,用最少的代码交出专业且有质感的加载体验。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考