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 实现流程
- 从设计稿或品牌色中选择一个 seed color(十六进制或整数)。
- 创建
MaterialColorBuilder实例,传入 seed color。 - 调用
buildLight()获取 Light 方案对象。 - 调用
buildDark()获取 Dark 方案对象。 - 将两个方案分别注入应用的 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:颜色方案构建流程
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:在业务组件中直接引用色值字符串,而非方案中的角色变量,导致后续切换失效。
常见错误 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 };
}