设计系统中的色彩体系构建
色彩是设计系统的核心要素之一,一个科学合理的色彩体系不仅能够提升产品的视觉表现,更能够传达品牌价值和改善用户体验。本文将深入探讨如何构建一套完整的设计系统色彩体系。
色彩理论基础
色彩模型
在数字设计中,我们主要使用以下色彩模型:
HSB/HSV 模型
- 色相 (Hue):色彩的基本属性,0-360°
- 饱和度 (Saturation):色彩的纯度,0-100%
- 明度 (Brightness/Value):色彩的亮度,0-100%
RGB 模型
- 红 (Red):0-255
- 绿 (Green):0-255
- 蓝 (Blue):0-255
HEX 表示法
十六进制色彩表示,如 #1890FF
色彩心理学
不同色彩会引发不同的心理反应:
typescript
interface ColorPsychology {
color: string
emotions: string[]
usage: string[]
}
const colorMeanings: ColorPsychology[] = [
{
color: 'blue',
emotions: ['信任', '专业', '稳定', '冷静'],
usage: ['主色调', '链接', '信息提示']
},
{
color: 'green',
emotions: ['成功', '自然', '成长', '和谐'],
usage: ['成功状态', '确认操作', '环保主题']
},
{
color: 'red',
emotions: ['紧急', '警告', '激情', '重要'],
usage: ['错误状态', '删除操作', '重要提醒']
},
{
color: 'orange',
emotions: ['活力', '创造', '温暖', '友好'],
usage: ['警告状态', '突出显示', '行动召唤']
}
]色彩体系架构
基础色彩定义
主色调 (Primary Colors)
主色调代表品牌核心色彩:
scss
// 主色调定义
$ghuo-blue-6: #1890ff; // 主色
$ghuo-blue-1: #e6f7ff; // 最浅
$ghuo-blue-2: #bae7ff;
$ghuo-blue-3: #91d5ff;
$ghuo-blue-4: #69c0ff;
$ghuo-blue-5: #40a9ff;
$ghuo-blue-6: #1890ff; // 标准色
$ghuo-blue-7: #096dd9;
$ghuo-blue-8: #0050b3;
$ghuo-blue-9: #003a8c;
$ghuo-blue-10: #002766; // 最深辅助色调 (Secondary Colors)
用于丰富视觉层次:
scss
// 成功色
$ghuo-green-6: #52c41a;
$ghuo-green-1: #f6ffed;
$ghuo-green-10: #135200;
// 警告色
$ghuo-orange-6: #faad14;
$ghuo-orange-1: #fff7e6;
$ghuo-orange-10: #ad4e00;
// 错误色
$ghuo-red-6: #f5222d;
$ghuo-red-1: #fff1f0;
$ghuo-red-10: #5c0011;中性色 (Neutral Colors)
用于文本、背景和边框:
scss
// 中性色定义
$ghuo-gray-1: #ffffff; // 纯白
$ghuo-gray-2: #fafafa; // 背景色
$ghuo-gray-3: #f5f5f5; // 分割线
$ghuo-gray-4: #f0f0f0; // 禁用背景
$ghuo-gray-5: #d9d9d9; // 边框色
$ghuo-gray-6: #bfbfbf; // 图标色
$ghuo-gray-7: #8c8c8c; // 辅助文字
$ghuo-gray-8: #595959; // 次要文字
$ghuo-gray-9: #434343; // 主要文字
$ghuo-gray-10: #262626; // 标题文字
$ghuo-gray-11: #1f1f1f; // 最深文字
$ghuo-gray-12: #141414; // 纯黑色彩生成算法
色彩梯度生成
基于主色自动生成色彩梯度:
typescript
interface ColorPalette {
50: string
100: string
200: string
300: string
400: string
500: string // 基准色
600: string
700: string
800: string
900: string
}
function generateColorPalette(baseColor: string): ColorPalette {
const hsl = hexToHsl(baseColor)
return {
50: hslToHex({ ...hsl, l: 95 }),
100: hslToHex({ ...hsl, l: 90 }),
200: hslToHex({ ...hsl, l: 80 }),
300: hslToHex({ ...hsl, l: 70 }),
400: hslToHex({ ...hsl, l: 60 }),
500: baseColor, // 基准色
600: hslToHex({ ...hsl, l: 45 }),
700: hslToHex({ ...hsl, l: 35 }),
800: hslToHex({ ...hsl, l: 25 }),
900: hslToHex({ ...hsl, l: 15 })
}
}
// 色彩空间转换函数
function hexToHsl(hex: string): { h: number; s: number; l: number } {
const r = parseInt(hex.slice(1, 3), 16) / 255
const g = parseInt(hex.slice(3, 5), 16) / 255
const b = parseInt(hex.slice(5, 7), 16) / 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const diff = max - min
let h = 0
let s = 0
const l = (max + min) / 2
if (diff !== 0) {
s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min)
switch (max) {
case r:
h = ((g - b) / diff + (g < b ? 6 : 0)) / 6
break
case g:
h = ((b - r) / diff + 2) / 6
break
case b:
h = ((r - g) / diff + 4) / 6
break
}
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100)
}
}语义化色彩
将色彩与具体用途关联:
typescript
interface SemanticColors {
// 品牌色彩
brand: {
primary: string
secondary: string
tertiary: string
}
// 功能色彩
functional: {
success: string
warning: string
error: string
info: string
}
// 界面色彩
interface: {
background: string
surface: string
border: string
divider: string
}
// 文本色彩
text: {
primary: string
secondary: string
disabled: string
inverse: string
}
}
const semanticColors: SemanticColors = {
brand: {
primary: '#1890ff',
secondary: '#722ed1',
tertiary: '#13c2c2'
},
functional: {
success: '#52c41a',
warning: '#faad14',
error: '#f5222d',
info: '#1890ff'
},
interface: {
background: '#ffffff',
surface: '#fafafa',
border: '#d9d9d9',
divider: '#f0f0f0'
},
text: {
primary: '#262626',
secondary: '#8c8c8c',
disabled: '#bfbfbf',
inverse: '#ffffff'
}
}无障碍设计
对比度标准
确保色彩符合 WCAG 2.1 标准:
typescript
// 对比度计算
function calculateContrast(color1: string, color2: string): number {
const luminance1 = getLuminance(color1)
const luminance2 = getLuminance(color2)
const lighter = Math.max(luminance1, luminance2)
const darker = Math.min(luminance1, luminance2)
return (lighter + 0.05) / (darker + 0.05)
}
function getLuminance(hex: string): number {
const rgb = hexToRgb(hex)
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(c => {
c = c / 255
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * r + 0.7152 * g + 0.0722 * b
}
// 对比度验证
interface ContrastCheck {
ratio: number
aa: boolean // WCAG AA 标准 (4.5:1)
aaa: boolean // WCAG AAA 标准 (7:1)
}
function checkContrast(foreground: string, background: string): ContrastCheck {
const ratio = calculateContrast(foreground, background)
return {
ratio,
aa: ratio >= 4.5,
aaa: ratio >= 7
}
}色盲友好设计
考虑色盲用户的需求:
typescript
// 色盲模拟
enum ColorBlindnessType {
Protanopia = 'protanopia', // 红色盲
Deuteranopia = 'deuteranopia', // 绿色盲
Tritanopia = 'tritanopia', // 蓝色盲
Monochromacy = 'monochromacy' // 全色盲
}
function simulateColorBlindness(color: string, type: ColorBlindnessType): string {
// 色盲模拟算法实现
const rgb = hexToRgb(color)
switch (type) {
case ColorBlindnessType.Protanopia:
return rgbToHex({
r: 0.567 * rgb.r + 0.433 * rgb.g,
g: 0.558 * rgb.r + 0.442 * rgb.g,
b: 0.242 * rgb.g + 0.758 * rgb.b
})
// 其他类型的实现...
default:
return color
}
}主题系统
多主题支持
支持亮色和暗色主题:
scss
// CSS 变量定义
:root {
// 亮色主题
--ghuo-color-bg-base: #ffffff;
--ghuo-color-bg-container: #ffffff;
--ghuo-color-bg-elevated: #ffffff;
--ghuo-color-text-base: #000000;
--ghuo-color-text-secondary: #666666;
--ghuo-color-border: #d9d9d9;
}
[data-theme='dark'] {
// 暗色主题
--ghuo-color-bg-base: #000000;
--ghuo-color-bg-container: #141414;
--ghuo-color-bg-elevated: #1f1f1f;
--ghuo-color-text-base: #ffffff;
--ghuo-color-text-secondary: #a6a6a6;
--ghuo-color-border: #434343;
}主题切换实现
typescript
interface ThemeConfig {
name: string
colors: Record<string, string>
}
class ThemeManager {
private currentTheme: string = 'light'
private themes: Map<string, ThemeConfig> = new Map()
registerTheme(theme: ThemeConfig) {
this.themes.set(theme.name, theme)
}
switchTheme(themeName: string) {
const theme = this.themes.get(themeName)
if (!theme) return
const root = document.documentElement
// 移除旧主题类
root.removeAttribute('data-theme')
// 应用新主题
root.setAttribute('data-theme', themeName)
// 更新 CSS 变量
Object.entries(theme.colors).forEach(([key, value]) => {
root.style.setProperty(`--ghuo-${key}`, value)
})
this.currentTheme = themeName
// 保存用户偏好
localStorage.setItem('ghuo-theme', themeName)
}
getCurrentTheme(): string {
return this.currentTheme
}
// 自动检测系统主题
detectSystemTheme() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)')
const updateTheme = () => {
this.switchTheme(prefersDark.matches ? 'dark' : 'light')
}
prefersDark.addEventListener('change', updateTheme)
updateTheme()
}
}色彩应用规范
组件色彩规范
按钮组件
scss
.ghuo-button {
// 主要按钮
&--primary {
background-color: var(--ghuo-color-primary);
border-color: var(--ghuo-color-primary);
color: #ffffff;
&:hover {
background-color: var(--ghuo-color-primary-hover);
border-color: var(--ghuo-color-primary-hover);
}
&:active {
background-color: var(--ghuo-color-primary-active);
border-color: var(--ghuo-color-primary-active);
}
&:disabled {
background-color: var(--ghuo-color-bg-disabled);
border-color: var(--ghuo-color-border);
color: var(--ghuo-color-text-disabled);
}
}
// 次要按钮
&--secondary {
background-color: transparent;
border-color: var(--ghuo-color-border);
color: var(--ghuo-color-text-base);
&:hover {
border-color: var(--ghuo-color-primary);
color: var(--ghuo-color-primary);
}
}
// 危险按钮
&--danger {
background-color: var(--ghuo-color-error);
border-color: var(--ghuo-color-error);
color: #ffffff;
}
}状态色彩
scss
// 状态指示器
.ghuo-status {
&--success {
color: var(--ghuo-color-success);
background-color: var(--ghuo-color-success-bg);
}
&--warning {
color: var(--ghuo-color-warning);
background-color: var(--ghuo-color-warning-bg);
}
&--error {
color: var(--ghuo-color-error);
background-color: var(--ghuo-color-error-bg);
}
&--info {
color: var(--ghuo-color-info);
background-color: var(--ghuo-color-info-bg);
}
}色彩使用指南
层级关系
typescript
interface ColorHierarchy {
level: number
usage: string
color: string
contrast: number
}
const textHierarchy: ColorHierarchy[] = [
{
level: 1,
usage: '标题文字',
color: 'var(--ghuo-color-text-heading)',
contrast: 7.0
},
{
level: 2,
usage: '正文文字',
color: 'var(--ghuo-color-text-base)',
contrast: 4.5
},
{
level: 3,
usage: '辅助文字',
color: 'var(--ghuo-color-text-secondary)',
contrast: 3.0
},
{
level: 4,
usage: '禁用文字',
color: 'var(--ghuo-color-text-disabled)',
contrast: 2.0
}
]色彩搭配原则
60-30-10 原则
- 60%:主色调(背景色)
- 30%:辅助色(内容区域)
- 10%:强调色(按钮、链接)
邻近色搭配
- 使用色相环上相邻的颜色
- 创造和谐统一的视觉效果
对比色搭配
- 使用色相环上相对的颜色
- 突出重要信息和操作
工具和流程
色彩管理工具
设计工具集成
typescript
// Figma 插件开发
interface FigmaColorToken {
name: string
value: string
type: 'color'
description?: string
}
class FigmaColorSync {
async exportTokens(): Promise<FigmaColorToken[]> {
const colorStyles = figma.getLocalPaintStyles()
return colorStyles.map(style => ({
name: style.name.replace(/\//g, '-').toLowerCase(),
value: this.paintToHex(style.paints[0]),
type: 'color',
description: style.description
}))
}
async importTokens(tokens: FigmaColorToken[]) {
for (const token of tokens) {
const paint: SolidPaint = {
type: 'SOLID',
color: this.hexToRgb(token.value)
}
const style = figma.createPaintStyle()
style.name = token.name
style.paints = [paint]
style.description = token.description || ''
}
}
private paintToHex(paint: Paint): string {
if (paint.type === 'SOLID') {
const { r, g, b } = paint.color
return `#${Math.round(r * 255).toString(16).padStart(2, '0')}${Math.round(g * 255).toString(16).padStart(2, '0')}${Math.round(b * 255).toString(16).padStart(2, '0')}`
}
return '#000000'
}
}自动化测试
typescript
// 色彩对比度测试
describe('Color Contrast Tests', () => {
const colorPairs = [
{ fg: '#1890ff', bg: '#ffffff', name: 'Primary on White' },
{ fg: '#ffffff', bg: '#1890ff', name: 'White on Primary' },
{ fg: '#262626', bg: '#ffffff', name: 'Text on White' },
{ fg: '#8c8c8c', bg: '#ffffff', name: 'Secondary Text on White' }
]
colorPairs.forEach(({ fg, bg, name }) => {
test(`${name} should meet WCAG AA standards`, () => {
const contrast = calculateContrast(fg, bg)
expect(contrast).toBeGreaterThanOrEqual(4.5)
})
})
})
// 主题一致性测试
describe('Theme Consistency Tests', () => {
test('All themes should have required color tokens', () => {
const requiredTokens = [
'color-primary',
'color-success',
'color-warning',
'color-error',
'color-text-base',
'color-bg-base'
]
const themes = ['light', 'dark']
themes.forEach(theme => {
requiredTokens.forEach(token => {
const value = getComputedStyle(document.documentElement)
.getPropertyValue(`--ghuo-${token}`)
expect(value).toBeTruthy()
expect(value).toMatch(/^#[0-9a-fA-F]{6}$/)
})
})
})
})文档生成
typescript
// 自动生成色彩文档
interface ColorDocumentation {
name: string
value: string
usage: string[]
contrast: { bg: string; ratio: number }[]
}
class ColorDocGenerator {
generateDocs(colors: Record<string, string>): ColorDocumentation[] {
return Object.entries(colors).map(([name, value]) => ({
name,
value,
usage: this.getColorUsage(name),
contrast: this.calculateContrastRatios(value)
}))
}
private getColorUsage(colorName: string): string[] {
const usageMap: Record<string, string[]> = {
'primary': ['按钮', '链接', '选中状态'],
'success': ['成功提示', '确认操作', '完成状态'],
'warning': ['警告提示', '注意事项', '待处理状态'],
'error': ['错误提示', '删除操作', '失败状态']
}
return usageMap[colorName] || []
}
private calculateContrastRatios(color: string): { bg: string; ratio: number }[] {
const backgrounds = ['#ffffff', '#f5f5f5', '#000000', '#141414']
return backgrounds.map(bg => ({
bg,
ratio: calculateContrast(color, bg)
}))
}
}最佳实践
1. 色彩命名规范
typescript
// 推荐的命名方式
const colorNaming = {
// ✅ 语义化命名
good: [
'color-primary',
'color-success',
'color-text-base',
'color-bg-container'
],
// ❌ 避免的命名
bad: [
'color-blue',
'color-light-gray',
'color-button-background'
]
}2. 渐进增强
scss
// 提供降级方案
.ghuo-button--primary {
background-color: #1890ff; /* 降级方案 */
background-color: var(--ghuo-color-primary, #1890ff);
color: white; /* 降级方案 */
color: var(--ghuo-color-primary-contrast, white);
}3. 性能优化
typescript
// 色彩预加载
const preloadColors = () => {
const link = document.createElement('link')
link.rel = 'preload'
link.as = 'style'
link.href = '/themes/colors.css'
document.head.appendChild(link)
}
// 懒加载主题
const loadTheme = async (themeName: string) => {
const { default: theme } = await import(`/themes/${themeName}.js`)
return theme
}总结
构建科学的色彩体系需要考虑:
- 理论基础:掌握色彩理论和心理学原理
- 系统性:建立完整的色彩层级和语义体系
- 无障碍性:确保色彩符合可访问性标准
- 一致性:在不同主题和场景下保持一致
- 可维护性:建立规范的命名和管理流程
- 工具支持:使用自动化工具提高效率
一个优秀的色彩体系不仅能够提升产品的视觉表现,更能够为用户提供清晰、一致的交互体验。通过科学的方法和工具,我们可以构建出既美观又实用的色彩系统。
发布于 2024年3月10日