4. 颜色方案基础配置:定义 Light 与 Dark 颜色方案、使用 MaterialColorBuilder

4.1 原理概述

Material Design 3 采用 HCT(Hue, Chroma, Tone)颜色空间,将光源色(seed color)映射为可动态调节的色板。Light 与 Dark 方案分别对应不同的 Tone 分布:Light 方案使用高亮度基底,Dark 方案使用低亮度基底,从而确保不同环境下的对比度与可读性。MaterialColorBuilder 是封装这一量化过程的构造器,接受一个 seed color 并输出两个完整的颜色方案对象。

核心概念:seed color 不直接作为主色使用,而是通过算法生成 Primary、Secondary、Tertiary、Error、Neutral 等色板。Light/Dark 方案共用同一套色相和色度,仅调整明度(Tone)。

4.2 实现流程

  1. 从设计稿或品牌色中选择一个 seed color(十六进制或整数)。
  2. 创建 MaterialColorBuilder 实例,传入 seed color。
  3. 调用 buildLight() 获取 Light 方案对象。
  4. 调用 buildDark() 获取 Dark 方案对象。
  5. 将两个方案分别注入应用的 ThemeProvider(或 MaterialApp)。

4.3 代码示例:使用 MaterialColorBuilder

以下 TypeScript 示例基于 @m3/color-builder 假设库,实际项目中可替换为 @material/material-color-utilities 的 DynamicScheme。

import { MaterialColorBuilder } from '@m3/color-builder';

// 1. 选择种子色(品牌紫)
const seed = '#6750A4';

// 2. 创建 builder
const builder = MaterialColorBuilder.fromSeed(seed);

// 3. 分别构建 Light 与 Dark 方案
const lightScheme = builder.buildLight();
const darkScheme = builder.buildDark();

// 4. 输出方案中的关键色值
console.log('Light - Primary:', lightScheme.primary);
console.log('Dark  - Primary:', darkScheme.primary);
console.log('Light - Surface:', lightScheme.surface);
console.log('Dark  - Surface:', darkScheme.surface);

4.4 颜色方案结构对比

颜色角色 Light 值(示例) Dark 值(示例) 说明
primary#6750A4#D0BCFF主色:Light 用深紫,Dark 用浅紫保持对比
onPrimary#FFFFFF#381E72主色上的文字色
secondary#625B71#CCC2DC次要交互色
surface#FFFBFE#1C1B1F背景基底
error#BA1A1A#FFB4AB错误状态色
background#FFFBFE#1C1B1F应用背景(常与 surface 一致)

4.5 内联 SVG:颜色方案构建流程

MaterialColorBuilder 构建 Light/Dark 方案流程 Seed Color MaterialColorBuilder Light 方案 Dark 方案 ThemeProvider / MaterialApp 说明 .fromSeed(seed) .buildLight() .buildDark()

4.6 调试与验证

  • 在浏览器控制台输出 lightScheme.primary 等值,使用 对比度检查器(如 Chrome DevTools “Color Contrast”)验证满足 WCAG AA 标准。
  • 使用 console.table() 将方案的所有颜色角色列成表格,快速检查异常。
  • 模拟系统 Dark 模式切换,确认两个方案均正确应用。
// 调试示例:将方案打印为表格
console.table({
  'Light Primary': lightScheme.primary,
  'Dark Primary': darkScheme.primary,
  'Light OnPrimary': lightScheme.onPrimary,
  'Dark OnPrimary': darkScheme.onPrimary,
});

4.7 部署:注入到应用主题

以 React + Material UI v5 with M3 为例,使用 ThemeProvider 包裹根组件:

import { ThemeProvider, createTheme } from '@mui/material/styles';
import { lightScheme, darkScheme } from './colorSchemes';

const lightTheme = createTheme(lightScheme);
const darkTheme = createTheme(darkScheme);

function App() {
  const [mode, setMode] = useState('light');
  const theme = mode === 'light' ? lightTheme : darkTheme;

  return (
    <ThemeProvider theme={theme}>
      <YourApp />
    </ThemeProvider>
  );
}

4.8 常见错误与避坑

常见错误 1:直接使用 seed color 作为 primary 值,忽略算法生成的色板。正确做法是始终使用 builder 输出,而非硬编码。
常见错误 2:只定义了 Light 方案,未定义 Dark 方案,导致 Dark 模式降级回到默认值(可读性差)。
常见错误 3:seed color 色度(Chroma)过低,导致方案颜色过于灰暗。建议选用色度 ≥ 36 的颜色。
常见错误 4:在业务组件中直接引用色值字符串,而非方案中的角色变量,导致后续切换失效。

4.9 性能与稳定性优化

  • 缓存 builder 结果:seed color 不会频繁变化,将 MaterialColorBuilder 实例或方案对象存为模块级单例,避免重复计算。
  • 延迟构建:在应用启动阶段并行构建 Light 和 Dark 方案,存入全局状态(如 React Context 或 Redux),首次渲染时直接读取。
  • 按需重建:仅当用户主动更换 seed color 时,才重新调用 builder。使用 useMemo 或 computed 依赖 seed color 变化。
  • 避免不必要的重新创建:不要在每次渲染时调用 MaterialColorBuilder.fromSeed,防止高频 GC 压力。
// 推荐:单例缓存
let cachedLight: ColorScheme | null = null;
let cachedDark: ColorScheme | null = null;
let cachedSeed = '';

function getColorSchemes(seed: string) {
  if (seed !== cachedSeed) {
    const builder = MaterialColorBuilder.fromSeed(seed);
    cachedLight = builder.buildLight();
    cachedDark = builder.buildDark();
    cachedSeed = seed;
  }
  return { light: cachedLight, dark: cachedDark };
}