antd ColorPicker 渐变色模式实战:用 `mode` 在单色与线性渐变之间自由切换
导读
ColorPicker 组件默认只能选取单一颜色,而通过 5.20.0 版本新增的 mode 配置,可以让同一个选择器同时支持"单色(single)"与"渐变(gradient)"两种取值形态,甚至在两者之间动态切换。本文以官方演示 line-gradient 为主线,结合仓库源码讲解 mode 的完整用法、渐变色值的数据结构、Color.toCssString() 的输出格式,以及模式切换背后状态同步的实现原理,读完后你可以直接在自己的表单与设计工具类场景中落地渐变取色能力。
mode 到底是什么:single 与 gradient 两种选择器模式
按官方 API 文档(见 index.zh-CN.md)的定义,ColorPicker 的 mode 属性用于配置选择器是"单色"还是"渐变":
| 参数 | 说明 | 类型 | 默认值 | 引入版本 |
|---|---|---|---|---|
| mode | 选择器模式,用于配置单色与渐变 | 'single' | 'gradient' | ('single' | 'gradient')[] |
single |
5.20.0 |
要点如下:
- 不传
mode:等效于'single',面板内只出现单色取色器(饱和度/明度面与色相滑块),这是组件默认行为; - 传
mode="gradient":面板直接呈现渐变编辑态,可拖拽色标、调整位置百分比; - 传
mode={['single', 'gradient']}:面板顶部会出现"单色 / 渐变"两个 Tab,允许用户在使用过程中自行切换两种形态,适合需求不确定或要兼顾两种取色能力的场景。
其类型定义位于 interface.ts:export type ModeType = 'single' | 'gradient';。此外,面板切换按钮所用的文案也来自组件国际化配置,在 locale 文件中对应 singleColor(单色)与 gradientColor(渐变)两项。
逐行拆解官方 demo:line-gradient.tsx
官方演示的完整源码见 line-gradient.tsx,核心代码如下:
import React from 'react';
import { ColorPicker, Space } from 'antd';
const DEFAULT_COLOR = [
{
color: 'rgb(16, 142, 233)',
percent: 0,
},
{
color: 'rgb(135, 208, 104)',
percent: 100,
},
];
const Demo = () => (
<Space vertical>
<ColorPicker
defaultValue={DEFAULT_COLOR}
allowClear
showText
mode={['single', 'gradient']}
onChangeComplete={(color) => {
console.log(color.toCssString());
}}
/>
<ColorPicker
defaultValue={DEFAULT_COLOR}
allowClear
showText
mode="gradient"
onChangeComplete={(color) => {
console.log(color.toCssString());
}}
/>
</Space>
);
export default Demo;
这个 demo 里其实同时示范了四条核心能力:
- 默认值即是一个渐变:
defaultValue传的是{ color, percent }数组,percent: 0表示渐变起点,percent: 100表示渐变终点,组件据此识别出这是一个渐变颜色(与单色字符串/Color对象区分开来); - 模式选择:第一个 ColorPicker 允许用户在单色/渐变间切换,第二个则锁定为
mode="gradient"; - 可清除:
allowClear允许清空所选颜色; - 结果输出:
showText在触发器上展示颜色文本,onChangeComplete在用户完成选择时回调,其参数是包装后的Color实例,通过color.toCssString()得到可直接用于 CSS 的字符串。
onChangeComplete 与单色模式下的 onChange(value, css) 回调不同,其回调签名在 index.zh-CN.md 中定义为 (value: Color) => void,并且受控使用 value 时,拖拽过程中展示的中间颜色不会被即时回写,从而避免受控场景下拖拽卡顿。
渐变色值的内部结构:ColorType 与颜色停靠点
defaultValue 之所以能表示渐变,是因为官方在 ColorType 类型中专门定义了颜色数组这一分支,见 index.zh-CN.md:
type ColorType =
| string
| Color
| {
color: string;
percent: number;
}[];
也就是说,ColorPicker 的取值状态分为三种形态:普通色字符串、选择器生成的 Color 对象、以及由若干"颜色 + 百分比"组成的渐变停靠点(gradient stops)数组。数组中的每一项都包含:
color:该停靠点的颜色值,示例中用的是rgb(16, 142, 233)这类 RGB 写法;percent:该停靠点在渐变线上的位置百分比,取值范围通常为0(起点)到100(终点)。
内部实现中,这一数组会被包装为 AggregationColor(定义在 color.ts)。AggregationColor 对外暴露了几个与渐变强相关的实例方法:
isGradient():返回true/false,用于判断当前颜色是否为渐变(实现见 color.ts);getColors():返回内部停靠点数组,非渐变时则退化为"自身作为唯一停靠点、percent 为 0"的单元素数组(见 color.ts);toCssString():将颜色序列化成 CSS 字符串。
toCssString() 的输出:linear-gradient(90deg, ...)
toCssString() 是官方在 5.20.0 加入并写进 API 文档的方法(见 index.zh-CN.md),其职责是"转换成 CSS 支持的格式"。对于渐变值,底层实现(见 color.ts)会先按百分比升序把各停靠点格式化为 rgb(...) X%,再拼接成标准线性渐变字符串:
// color.ts 中 toCssString 的核心逻辑(示意)
const colorsStr = colors.map((c) => `${c.color.toRgbString()} ${c.percent}%`).join(', ');
return `linear-gradient(90deg, ${colorsStr})`;
因此 demo 中 DEFAULT_COLOR(蓝色 → 绿色,0% → 100%)经 color.toCssString() 得到的实际输出形如:
linear-gradient(90deg, rgb(16, 142, 233) 0%, rgb(135, 208, 104) 100%)
这条字符串可以直接赋给 CSS 的 background/background-image 属性,从而让组件与业务样式无缝衔接。而非渐变的单色值走的是另一分支,直接返回 this.metaColor.toRgbString() 形式的 rgb(r, g, b) 字符串。
模式与颜色如何保持同步:useModeColor 的内部逻辑
当 mode 允许两种模式而用户又改了颜色形态时,组件必须保证"当前展示的模式"与"当前颜色值的形态"始终一致,这一职责由自定义 Hook useModeColor.ts 承担。从源码可以归纳出以下关键设计:
- 模式列表归一化:入参
mode被统一归一化为数组,过滤空值;若为空则回退为['single'](对应默认值single),随后按single、gradient的顺序生成面板 Tab 的可选项(见 useModeColor.ts); - 模式跟随颜色自动对齐:每当颜色值变化,都会执行
setModeState(postColor.isGradient() ? 'gradient' : 'single'),即"渐变值出现 → 面板自动切到渐变模式;清成单色 → 自动切回单色模式"(见 useModeColor.ts); - 受限模式下的兜底:如果外部传入的
mode不允许当前内部 modeState(例如只允许gradient却被切到了single),postMode会回退到可选项中的第一个,从而保证渲染的模式永远落在合法集合内(见 useModeColor.ts)。
也就是说,即使你只传 mode="gradient" 而不关心颜色形态,组件内部也会用颜色自身的渐变/单色状态去对齐当前激活的 Tab,开发者无需自己维护两套状态。
单色 ↔ 渐变切换时的体验细节:颜色缓存策略
在 ColorPicker.tsx 中还有一个值得注意的体验优化:当用户在单色与渐变间切换模式时,源码注释明确写道:
To enhance user experience, we cache the gradient color when switch from gradient to single. If user not modify single color, we will use the cached gradient color.
对应的实现逻辑是(见 ColorPicker.tsx 附近的处理):
- 从渐变切到单色时,会取渐变第一个停靠点的颜色作为单色值:
onInternalChange(new AggregationColor(mergedColor.getColors()[0].color)); - 但从单色切回渐变时,如果用户没有手动修改过单色,组件会优先恢复之前缓存的渐变颜色(
cachedGradientColor),避免切换后渐变配置丢失; - 拖拽渐变过程中的中间态由
gradientDragging状态标记,配合onChangeComplete保证"拖拽预览"与"完成提交"两种语义的分离。
进阶:用 presets 提供预设渐变
如果你希望把渐变作为可一键选中的预设提供给用户,可以参考同一主题下的 presets-line-gradient 演示 及其源码 presets-line-gradient.tsx。其做法是把若干组"两个颜色停靠点"组合成预设项:
const PRESET_COLORS = [
['rgb(42, 188, 191)', 'rgb(56, 54, 221)'],
['rgb(34, 73, 254)', 'rgb(199, 74, 168)'],
['rgb(255, 111, 4)', 'rgb(243, 48, 171)'],
['rgb(244, 170, 6)', 'rgb(229, 70, 49)'],
];
<ColorPicker
mode={['single', 'gradient']}
presets={[
{
label: 'Liner',
defaultOpen: true,
colors: PRESET_COLORS.map((colors) => [
{ color: colors[0], percent: 0 },
{ color: colors[1], percent: 100 },
]),
},
]}
/>
presets 的类型为 PresetColorType:
type PresetColorType = {
label: React.ReactNode;
defaultOpen?: boolean;
key?: React.Key;
colors: ColorType[];
};
其中每个分组的 colors 直接复用 ColorType,因此既可以放单色字符串,也可以放"颜色 + percent"数组构成的渐变。defaultOpen: true 让该预设分组在面板打开时默认展开,用户点击即可应用渐变,通常和 mode={['single', 'gradient']} 搭配使用更顺手。
测试与行为验证
仓库的 gradient.test.tsx 对渐变行为给出了可验证的断言,例如:
- 当选择结果由渐变完成时,
color.toCssString()返回linear-gradient(90deg, rgb(255, 0, 0) 0%)这类标准字符串; - 多停靠点渐变同样会被正确序列化为完整的
linear-gradient(...)表达式。
此外,color.ts 中 equals() 的实现说明两个渐变颜色的相等判断是逐停靠点比较的:要求停靠点数量一致,且每个停靠点的 percent 与颜色逐一相等。这也提醒开发者:若在受控场景下想判断"渐变是否发生变化",不能只比较 toCssString() 之外的非规范化字符串,应统一使用组件生成的 Color 对象进行比对。
受控使用与 FAQ:为什么推荐用 Color 对象赋值
渐变涉及多个停靠点,若受控值来自手工拼接的颜色字符串,再做格式互转会引入精度误差。官方 FAQ(见 index.zh-CN.md)明确建议:受控场景推荐直接使用选择器生成的 Color 对象来赋值,而不是字符串色值,这样可以避免不同格式间的换算精度问题,保证取值精准、选择器按预期工作。
综合来看,mode 与渐变能力使得 antd ColorPicker 从"单一取色器"升级为"可承载设计稿色板"的取值控件:给 defaultValue 一组 { color, percent } 即可回填渐变,配合 mode 控制切换自由度,再用 onChangeComplete 的 toCssString() 把结果无缝注入业务样式。相关源码文件(useModeColor.ts、color.ts、ColorPicker.tsx)与测试(gradient.test.tsx)都可以作为进一步深入研读的起点。
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00