Ant Design Image 占位符 placeholder 深入实践:水彩水墨动画、进度条与自定义节点
本篇技术指南聚焦 Ant Design Image 组件的 placeholder 属性,结合官方 Demo 与仓库源码,系统讲解「{ progress: true } 水彩墨水加载动画」「{ progress: { percent } } 进度条」以及「自定义 React 节点占位」三种用法,并深入剖析其底层实现原理与语义化定制能力。读完即可在真实业务(如图片/AI 图片渐进加载)中直接落地这套占位与进度展示方案。
一、placeholder 能力总览:三种用法与类型定义
Ant Design Image 的 placeholder 用于定义图片加载过程中的占位内容。根据官方 Demo(渐进加载演示说明 与对应实现 placeholder.tsx),它支持三种形态:
{ progress: true }:显示内置的水彩墨水(watercolor ink)加载动画,适合「不知道进度」的等待场景;{ progress: { percent: number } }:显示一条带动画渐变效果的进度条,并在下方以百分比文字指示当前进度;- 自定义 React 节点:完全由使用者绘制占位 UI,适合品牌化、毛玻璃等定制视觉。
在类型层面,这三种用法被统一收敛为 PlaceholderType(见 index.tsx):
export interface ImageProgressConfig {
percent?: number;
/** 自定义渲染函数,接收默认进度 UI 与百分比 */
render?: (progress: React.ReactNode, percent: number) => React.ReactNode;
}
export type PlaceholderType =
| React.ReactNode
| {
progress?: boolean | ImageProgressConfig;
};
官方 API 文档(index.zh-CN.md)中对应说明为:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| placeholder | 加载占位,支持 ReactNode 或配置对象 | PlaceholderType |
- |
| progress | 进度配置,设置为 true 显示渐变动画,设置 { percent: number } 显示进度,render 自定义渲染 |
boolean | ImageProgressConfig |
- |
| percent | 进度值 | number |
- |
| render | 自定义渲染,接收默认的进度 UI 和百分比 | (progress, percent) => React.ReactNode |
- |
值得注意的是:placeholder 并未接入 ConfigProvider 的全局组件级配置(API 表格中该行「全局配置」列为 ×),因此它始终需要按组件单独传入。
二、{ progress: true }:内置水彩墨水加载动画
当不需要展示具体进度、只需一个「正在加载」的动效时,直接写:
<Image width={200} height={200} placeholder={{ progress: true }} />
2.1 布尔值背后的配置归一化
{ progress: true } 会被 usePlaceholderConfig 这一 Hook 归一化为「无 percent 的进度配置」:
export default function usePlaceholderConfig(placeholder?: PlaceholderType) {
return useMemo(() => {
if (!placeholder || !isPlaceholderConfig(placeholder)) {
return {};
}
if (typeof placeholder.progress === 'boolean') {
return {
progressConfig: placeholder.progress ? {} : undefined,
};
}
return {
progressConfig: placeholder.progress,
};
}, [placeholder]);
}
关键点是区分「配置对象」与「React 节点」:只有普通对象且不是合法 React 元素时才被当作进度配置(isPlaceholderConfig),因此直接传入一段 JSX 会被判定为自定义节点占位而不会走进度逻辑。
2.2 五种「墨色水彩层」的实现原理
当进度配置生效后,index.tsx 会直接渲染 Progress 组件(而非底层 rc-image),其视觉核心是两枚 div 加上伪元素共 5 层径向渐变(radial-gradient)+ 高斯模糊的「墨水团」:
ink-1:左上蓝色云团 +::before中右薰衣草紫 +::after底部青色;ink-2:粉色花瓣点缀 +::before淡长春花蓝点缀。
每层的尺寸为容器 150%、向左上偏移 25%,配合 filter: blur(35~45px) 与 backdrop-filter: blur(8px) 的背景营造出湿润、晕染的水墨质感(见 style/index.ts)。动画则由 progressAnimation.ts 提供的 inkFlow1/2/3 关键帧驱动:
export const inkFlow1 = createInkFlow('antImageInkFlow1', 'translate(15%, -20%) scale(1.25)', 0.5);
export const inkFlow2 = createInkFlow(
'antImageInkFlow2',
'translate(-18%, 15%) scale(0.85)',
0.9,
'translate(0%, 0%) scale(1.1)',
0.7,
);
关键帧在平移缩放的同时周期性调整透明度(0.5~0.9),5 层各自使用不同的 animation-duration 与负延迟(如 calc(duration + 2s)、-1s)错峰运动,形成层次丰富的流动感。动画时长由主题 token progressAnimationDuration 控制,默认 3s(见 style/index.ts)。
三、{ progress: { percent } }:带百分比的进度条
当上游(如上传任务、AI 图像生成)能持续回报进度时,可以这样使用:
<Image
width={200}
height={200}
placeholder={{ progress: { percent: 50 } }}
/>
3.1 percent 的边界处理与无障碍设计
查看 Progress.tsx 的实现可以发现几个工程细节:
// percent 必须是有限数字才算有效
const hasPercent = isNumber(percent) && Number.isFinite(percent);
// 宽度按 0-100 截断取整,避免进度条溢出
const percentValue = hasPercent ? Math.max(0, Math.min(100, Math.round(percent))) : 0;
// 有进度时声明为 progressbar 并同步 ARIA 属性
const ariaProps = hasPercent
? {
role: 'progressbar',
'aria-valuemin': 0,
'aria-valuemax': 100,
'aria-valuenow': percentValue,
'aria-label': `${percentValue}%`,
}
: { 'aria-busy': true };
- percent 会被自动收窄到
[0, 100]并取整,即使传入负数或大于 100 也不会破坏布局; - 传入真实数字时,外层 DOM 具备
role="progressbar"及完整的aria-valuenow/min/max语义; - 不传 percent(即第二节的纯动画模式)时则标记为
aria-busy,并附带一个视觉隐藏的role="status"+aria-live="polite"提示「Loading」,保证读屏器能感知加载中状态。
3.2 进度条的轨道与流光效果
进度条本体是一条 6px 高的 rail,其填充宽度通过 CSS 变量 --progress-percent 驱动:
<div className={`${prefixCls}-progress-rail`}
style={{ '--progress-percent': `${percentValue}%` } as React.CSSProperties} />
对应样式(style/index.ts)用 ::before 渲染渐变轨道,width 指向该变量并带 transition 平滑过渡;同时叠加 progressActive 关键帧,使轨道内渐变背景从 200% 0 往复移动至 -200% 0,形成持续流动的微光扫过效果。轨道上方(或下方,随垂直排列布局)会以主题色文本展示 50% 这类百分比指示(indicator)。
四、render 自定义进度 UI:基于默认进度条的二次加工
如果不满意默认的「进度条 + 百分比文字」排版,ImageProgressConfig.render 允许接收「默认进度 UI」与「当前百分比」两个参数自行组合。官方 Demo 给出了两种写法:
// 完全自定义:忽略默认进度条,渲染纯文本
<Image
width={200}
height={200}
placeholder={{ progress: { render: () => 'loading...' } }}
/>
// 在默认进度条下追加文字说明,实现「进度条 + 语义提示」混排
<Image
width={200}
height={200}
placeholder={{
progress: {
percent: 75,
render: (progress, p) => (
<>
{progress}
<div style={{ marginTop: 8 }}>Generating {p}%</div>
</>
),
},
}}
/>
从 Progress.tsx 可以看到,render(progressBar, percentValue) 的第一个参数是默认的进度条节点(未提供 percent 时为 null),第二个参数是收窄取整后的百分比数字——它同样是 p 的真实来源,因此自定义渲染中既能复用官方轨道,又能自由排版。
五、自定义 React 节点占位:把占位完全握在自己手里
第三种用法是直接传入一段 JSX,例如 Demo 中模拟「加载骨架 + 毛玻璃底」的占位:
<Image
width={200}
height={200}
src={imageUrl}
placeholder={
<div
style={{
width: '100%',
height: '100%',
background: 'rgba(255, 255, 255, 0.3)',
backdropFilter: 'blur(10px)',
borderRadius: token.borderRadiusLG,
}}
/>
}
/>
自定义节点会被当作原生占位交给底层 RcImage 渲染(见 index.tsx)。值得留意的边界场景:当 src 未提供时 rc-image 会直接进入 error 状态,因此源码中有专门判断——placeholderNode && !src 时将自定义节点作为覆盖层(overlay)渲染,避免「无图即报错」;一旦提供了 src,占位则按原生加载流程在图片就绪前展示。Demo 里配合「Reload Image」按钮不断给 src 追加时间戳查询参数来触发重新加载,正是为了让占位节点在真实环境中反复可见。
六、实战:完整模拟「AI 图片渐进生成」加载流程
Demo 第二段中的 GeneratingProgress 组件是一个可以搬到业务中的完整范式:先用进度条模拟漫长的生成过程,生成完成后再切换为真实图片。其核心状态机如下(见 placeholder.tsx):
const [percent, setPercent] = useState(0);
const [status, setStatus] = useState<'idle' | 'generating' | 'complete'>('idle');
useEffect(() => {
if (status === 'generating' && percent < 100) {
// 每 200ms 随机累加进度,模拟服务端回报
const timer = setTimeout(() => {
setPercent((prev) => Math.min(prev + Math.random() * 8 + 2, 100));
}, 200);
return () => clearTimeout(timer);
} else if (status === 'generating' && percent >= 100) {
const timer = setTimeout(() => setStatus('complete'), 200);
return () => clearTimeout(timer);
}
}, [status, percent]);
渲染时依据状态切换两条分支:
complete:渲染带src的Image,正式显示生成结果;generating:渲染不带src的Image,并传入placeholder={{ progress: { percent, render } }}——由于此时没有src且是进度配置,组件会直接渲染全尺寸的进度覆盖层,视觉上严格占据 200×200 的图片占位区域(这正是前面「showProgressOverlay」分支的作用)。
这种「先占位、后出图」的写法是图片占位技术最常见的落地场景,可在生成型产品(绘图、扩图、AI 头像等)中直接复用。
七、语义化定制:styles / classNames 精确控制占位结构
若需调整占位内部细节(例如进度条高度、百分比文字间距),不必写全局 CSS 覆盖,v6 的语义化 API 已覆盖占位结构。在 index.tsx 的 ImageSemanticType 中,placeholder.progress 暴露了 root / content / rail / indicator 四段语义结构,对应 Progress.tsx 中定义的同名 ProgressClassNames / ProgressStyles:
<Image
width={200}
height={200}
placeholder={{ progress: { percent: 60 } }}
styles={{
placeholder: {
progress: {
root: { borderRadius: 12 }, // 整个进度容器
rail: { height: 8 }, // 进度轨道
indicator: { color: '#1677ff' }, // 百分比文字
},
},
}}
/>
这样既能保持视觉与主题统一,又能对占位细节做像素级微调。
八、小结:如何选择三种占位方案
| 场景 | 推荐写法 |
|---|---|
| 只需提示「加载中」,无进度可报 | placeholder={{ progress: true }} |
| 上游有实时进度回报(上传/AI 生成/请求分块) | placeholder={{ progress: { percent } }},配合 render 自定文案 |
| 需要完全定制视觉(骨架屏、毛玻璃、品牌插画) | 直接传入 React 节点,如 placeholder={<Skeleton />} |
| 进度归零起步、百分比需要本地自增驱动 | 参照上文「GeneratingProgress」状态机范式 |
以上三种用法的定义均可追溯至 PlaceholderType 类型、usePlaceholderConfig 归一化 Hook、Progress 渲染组件 与 占位样式 的完整链路,读者可在这些文件中继续追查水彩动画、进度轨道与无障碍属性的全部细节。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00