UI 色彩系统的数学之美:从色轮理论到算法生成色板的底层原理
UI 色彩系统的数学之美:从色轮理论到算法生成色板的底层原理
一、引子:为什么"好看的配色"可以写成算法
设计师花四年学的色彩理论,本质上是一组数学公式在 HSL 色彩空间上的操作。互补色 = 色相 + 180°,三等分配色 = 色相 + 120°/240°,类比色 = 色相 ± 30°。理解这些数学关系后,程序化生成色板就从一个"美学问题"变成了一个"参数搜索问题"。
二、色彩空间的数学表示
OKLCH 的优势
人眼对黄色的亮度感知强于蓝色。HSL 的 L=50%(亮度)对于黄色感觉"很亮",对于蓝色感觉"很暗"。OKLCH 修正了这一点——同一 L 值的不同色相在人眼中感知亮度一致。这对无障碍设计(保证对比度)至关重要。
三、算法生成色板的数学公式
/** * 基于 OKLCH 的色板生成算法 */ interface ColorPalette { name: string; primary: string; // 主色 variants: string[]; // 色阶 (50, 100, ..., 900) complements: string[]; // 互补色 } /** * 从单一主色生成完整色板 * * @param baseColor OKLCH 格式 { l, c, h } * @param mode 'monochromatic' | 'analogous' | 'complementary' | 'triadic' */ function generatePalette( baseColor: OKLCH, mode: ColorMode ): ColorPalette { // 1. 生成主色色阶(亮度变化) const variants = generateShades(baseColor); // 2. 根据配色模式生成辅助色 const complements = generateComplements(baseColor, mode); // 3. 确保所有颜色满足 WCAG AA 对比度 const validated = validateContrast([...variants, ...complements]); return { name: `Palette-${baseColor.h}`, primary: toHex(validated[5]), // 500 作为主色 variants: validated.slice(0, 10).map(toHex), complements: validated.slice(10).map(toHex), }; } /** * 生成色阶(从浅到深 10 阶) * * 算法逻辑: * - L 值从 0.98(极浅)到 0.15(极深)均匀分布 * - c 值在中间色阶最高,两端递减(极浅和极深容不下高色度) * - h 值保持恒定 */ function generateShades(base: OKLCH): OKLCH[] { const shades: OKLCH[] = []; const numShades = 10; for (let i = 0; i < numShades; i++) { const t = i / (numShades - 1); // 0 → 1 // L: 从 0.98 到 0.15 线性分布 const l = 0.98 - t * 0.83; // c: 中间色阶色度最高(抛物线分布) const c = base.c * (1 - 4 * Math.pow(t - 0.5, 2)); // h: 保持恒定 const h = base.h; shades.push({ l, c, h }); } return shades; } /** * 生成互补色/辅助色 */ function generateComplements(base: OKLCH, mode: ColorMode): OKLCH[] { switch (mode) { case 'complementary': // 对立色相:+ 180° return [{ l: base.l, c: base.c * 0.8, h: (base.h + 180) % 360 }]; case 'triadic': // 三等分:+ 120°, + 240° return [ { l: base.l, c: base.c * 0.7, h: (base.h + 120) % 360 }, { l: base.l, c: base.c * 0.7, h: (base.h + 240) % 360 }, ]; case 'analogous': // 相似色:± 30° return [ { l: base.l * 0.95, c: base.c * 0.9, h: (base.h + 30) % 360 }, { l: base.l * 0.95, c: base.c * 0.9, h: (base.h + 330) % 360 }, ]; case 'split-complementary': // 分裂互补:+ 150°, + 210° return [ { l: base.l, c: base.c * 0.7, h: (base.h + 150) % 360 }, { l: base.l, c: base.c * 0.7, h: (base.h + 210) % 360 }, ]; default: return []; } } /** * 验证对比度 * WCAG AA: 正常文本 4.5:1, 大文本 3:1 */ function validateContrast(colors: OKLCH[]): OKLCH[] { return colors.map((color) => { // 在白色背景 (#FFFFFF) 上检查对比度 const contrastOnWhite = calculateContrast(color, WHITE); // 如果对比度不足,降低 L 值增大对比 if (contrastOnWhite < 4.5) { const adjustment = Math.pow(4.5 / contrastOnWhite, 0.5); return { ...color, l: color.l * (1 / adjustment) }; } return color; }); } /** * 计算两个 OKLCH 颜色之间的 WCAG 对比度 */ function calculateContrast(color: OKLCH, bg: OKLCH): number { // 将 OKLCH 转为相对亮度(Y 分量) const lum1 = oklchToRelativeLuminance(color); const lum2 = oklchToRelativeLuminance(bg); const lighter = Math.max(lum1, lum2); const darker = Math.min(lum1, lum2); return (lighter + 0.05) / (darker + 0.05); } // 类型定义 interface OKLCH { l: number; c: number; h: number; } type ColorMode = 'monochromatic' | 'analogous' | 'complementary' | 'triadic' | 'split-complementary'; const WHITE: OKLCH = { l: 1.0, c: 0, h: 0 }; function toHex(color: OKLCH): string { /* OKLCH → HEX 转换 */ return '#000000'; } function oklchToRelativeLuminance(c: OKLCH): number { return 0.5; }四、从数学到美学:人的判断不可替代
算法生成的色板在数学上是"和谐的"——色相分布均匀、对比度达标。但数学上的和谐不保证审美上的愉悦。两个色相差 180° 的颜色在理论上互补,但特定饱和度和亮度的组合可能刺眼或沉闷。
算法的角色是生成候选色板(人类设计师不可能手动尝试 360° × 100% × 100% 空间内的所有组合),设计师的角色是从候选方案中选择最符合品牌调性的那一组。
推荐工作流:
- 算法生成 20 组候选色板
- 设计师筛选 3 组
- 在真实 UI 场景中做压力测试
- 最终确定 1 组
结论
- 色彩理论本质是 HSL/OKLCH 空间上的数学操作
- 互补色 = 色相 + 180°,三等分 = + 120°/240°,类比色 = ± 30°
- OKLCH 优于 HSL,因为感知亮度均匀,更适合无障碍设计
- 色阶生成的核心是 L 线性分布 + c 抛物线分布
- 算法生成候选色板,设计师筛选最优——人机协作模式
- WCAG 对比度验证必须嵌入算法中,自动修正不达标的颜色
