CSS color-mix()関数の実践活用:動的カラー生成テクニック


color-mix()とは

color-mix()は、2つの色を指定した比率で混ぜ合わせる新しいCSS関数です。従来のSassやPostCSSでしか実現できなかった動的な色生成が、純粋なCSSだけで可能になりました。

基本構文

color-mix(in <colorspace>, <color1> <percentage>, <color2> <percentage>)

ブラウザサポート

  • Chrome 111+
  • Firefox 113+
  • Safari 16.2+
  • Edge 111+

基本的な使い方

シンプルな色の混合

/* 50%ずつ混ぜる */
.element {
  background: color-mix(in srgb, blue, yellow);
  /* 結果: 緑がかった色 */
}

/* 比率を指定 */
.element {
  background: color-mix(in srgb, blue 70%, yellow 30%);
  /* 青寄りの色 */
}

/* 片方だけ指定(残りは自動計算) */
.element {
  background: color-mix(in srgb, blue 25%, yellow);
  /* blue 25% + yellow 75% */
}

カラースペースの選択

/* sRGB色空間(デフォルト) */
.srgb {
  background: color-mix(in srgb, red, blue);
}

/* OKLCH色空間(知覚的に均一) */
.oklch {
  background: color-mix(in oklch, red, blue);
}

/* HSL色空間 */
.hsl {
  background: color-mix(in hsl, red, blue);
}

/* LAB色空間 */
.lab {
  background: color-mix(in lab, red, blue);
}

カラースペースによる違い:

:root {
  /* sRGBは数学的に混合(やや不自然) */
  --mix-srgb: color-mix(in srgb, #ff0000, #0000ff);
  
  /* OKLCHは人間の知覚に基づく混合(自然) */
  --mix-oklch: color-mix(in oklch, #ff0000, #0000ff);
  
  /* HSLは色相環に沿って混合 */
  --mix-hsl: color-mix(in hsl, #ff0000, #0000ff);
}

デザインシステムでの活用

ブランドカラーの派生色生成

:root {
  /* ベースカラー */
  --brand-primary: #3b82f6;
  --brand-secondary: #8b5cf6;
  
  /* 明るいバリエーション */
  --primary-50: color-mix(in oklch, var(--brand-primary) 5%, white);
  --primary-100: color-mix(in oklch, var(--brand-primary) 10%, white);
  --primary-200: color-mix(in oklch, var(--brand-primary) 25%, white);
  --primary-300: color-mix(in oklch, var(--brand-primary) 40%, white);
  --primary-400: color-mix(in oklch, var(--brand-primary) 60%, white);
  --primary-500: var(--brand-primary);
  
  /* 暗いバリエーション */
  --primary-600: color-mix(in oklch, var(--brand-primary) 85%, black);
  --primary-700: color-mix(in oklch, var(--brand-primary) 70%, black);
  --primary-800: color-mix(in oklch, var(--brand-primary) 50%, black);
  --primary-900: color-mix(in oklch, var(--brand-primary) 30%, black);
}

.button-primary {
  background: var(--primary-500);
  color: white;
}

.button-primary:hover {
  background: var(--primary-600);
}

.button-primary:active {
  background: var(--primary-700);
}

透明度のコントロール

:root {
  --brand-color: #3b82f6;
  
  /* 透明度のバリエーション */
  --brand-alpha-10: color-mix(in srgb, var(--brand-color) 10%, transparent);
  --brand-alpha-20: color-mix(in srgb, var(--brand-color) 20%, transparent);
  --brand-alpha-50: color-mix(in srgb, var(--brand-color) 50%, transparent);
  --brand-alpha-80: color-mix(in srgb, var(--brand-color) 80%, transparent);
}

.overlay {
  background: var(--brand-alpha-50);
  backdrop-filter: blur(8px);
}

.shadow {
  box-shadow: 0 4px 6px var(--brand-alpha-20);
}

ダークモード対応

自動的な色の調整

:root {
  --bg-base: white;
  --text-base: #1a1a1a;
  --surface-color: #f5f5f5;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg-base: #1a1a1a;
    --text-base: white;
    --surface-color: #2a2a2a;
  }
}

/* ベース色を基に派生色を生成 */
.card {
  background: var(--surface-color);
  border: 1px solid color-mix(in srgb, var(--text-base) 10%, transparent);
  color: var(--text-base);
}

.card:hover {
  background: color-mix(in srgb, var(--surface-color), var(--text-base) 5%);
}

セマンティックカラーの生成

:root {
  --brand-primary: #3b82f6;
  
  /* ライトモード */
  --color-primary: var(--brand-primary);
  --color-primary-hover: color-mix(in oklch, var(--brand-primary) 85%, black);
  --color-primary-text: color-mix(in oklch, var(--brand-primary) 20%, black);
  
  /* 背景色に対するコントラスト調整 */
  --bg-primary: color-mix(in oklch, var(--brand-primary) 10%, white);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-primary: color-mix(in oklch, var(--brand-primary) 80%, white);
    --color-primary-hover: color-mix(in oklch, var(--brand-primary) 90%, white);
    --color-primary-text: color-mix(in oklch, var(--brand-primary) 60%, white);
    --bg-primary: color-mix(in oklch, var(--brand-primary) 15%, black);
  }
}

インタラクティブな状態管理

ホバー・フォーカス状態

.interactive-button {
  --base-color: #3b82f6;
  
  background: var(--base-color);
  color: white;
  transition: background 0.2s;
}

.interactive-button:hover {
  background: color-mix(in oklch, var(--base-color) 90%, white);
}

.interactive-button:active {
  background: color-mix(in oklch, var(--base-color) 85%, black);
}

.interactive-button:focus-visible {
  outline: 2px solid var(--base-color);
  outline-offset: 2px;
  background: color-mix(in oklch, var(--base-color) 95%, white);
}

.interactive-button:disabled {
  background: color-mix(in srgb, var(--base-color) 30%, gray);
  cursor: not-allowed;
  opacity: 0.6;
}

進行状態の視覚化

.progress-indicator {
  --progress-color: #10b981;
  --progress-value: 65%; /* JavaScriptから動的に設定 */
  
  width: 100%;
  height: 8px;
  background: color-mix(in srgb, var(--progress-color) 20%, transparent);
  border-radius: 4px;
  overflow: hidden;
}

.progress-indicator::before {
  content: '';
  display: block;
  width: var(--progress-value);
  height: 100%;
  background: linear-gradient(
    90deg,
    var(--progress-color),
    color-mix(in oklch, var(--progress-color), white 30%)
  );
  transition: width 0.3s ease;
}

グラデーションとの組み合わせ

動的グラデーション生成

.gradient-card {
  --base-color: #6366f1;
  
  background: linear-gradient(
    135deg,
    var(--base-color),
    color-mix(in oklch, var(--base-color), #ec4899 50%)
  );
}

/* 複数のストップポイント */
.complex-gradient {
  --primary: #3b82f6;
  --secondary: #8b5cf6;
  
  background: linear-gradient(
    to right,
    var(--primary),
    color-mix(in oklch, var(--primary) 70%, var(--secondary) 30%),
    color-mix(in oklch, var(--primary) 50%, var(--secondary) 50%),
    color-mix(in oklch, var(--primary) 30%, var(--secondary) 70%),
    var(--secondary)
  );
}

グラデーションオーバーレイ

.image-card {
  position: relative;
  overflow: hidden;
}

.image-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, black 0%, transparent),
    color-mix(in srgb, black 70%, transparent)
  );
}

アクセシビリティ対応

コントラスト比の自動調整

:root {
  --brand-color: #3b82f6;
  --bg-color: white;
}

/* 背景色に対して十分なコントラストを確保 */
.accessible-text {
  background: var(--bg-color);
  color: color-mix(in oklch, var(--brand-color) 70%, black);
  /* より暗くすることでコントラスト比を向上 */
}

/* ボタンのコントラスト */
.accessible-button {
  background: var(--brand-color);
  /* 背景色が明るい場合は暗いテキスト、暗い場合は明るいテキスト */
  color: color-mix(in oklch, var(--brand-color) 10%, white);
}

@media (prefers-contrast: more) {
  .accessible-button {
    /* 高コントラストモードでより強調 */
    background: color-mix(in oklch, var(--brand-color) 85%, black);
    color: white;
    border: 2px solid currentColor;
  }
}

色覚異常への配慮

/* 色だけに頼らない表現 */
.status-indicator {
  --status-color: #10b981; /* success green */
  
  background: color-mix(in srgb, var(--status-color) 15%, transparent);
  border-left: 4px solid var(--status-color);
  color: color-mix(in oklch, var(--status-color) 60%, black);
}

.status-indicator::before {
  content: '✓ ';
  /* アイコンで状態を補足 */
}

/* 色相だけでなく明度も変える */
.error {
  --error-base: #ef4444;
  background: color-mix(in oklch, var(--error-base) 10%, white);
  border: 2px solid color-mix(in oklch, var(--error-base) 70%, black);
}

実践的なパターン

カラーパレットジェネレーター

@property --hue {
  syntax: '<number>';
  inherits: false;
  initial-value: 200;
}

.palette-generator {
  --base-hue: var(--hue);
  --saturation: 70%;
  --lightness: 50%;
  
  /* ベースカラー */
  --color-1: oklch(var(--lightness) var(--saturation) var(--base-hue));
  
  /* 類似色 */
  --color-2: oklch(var(--lightness) var(--saturation) calc(var(--base-hue) + 30));
  --color-3: oklch(var(--lightness) var(--saturation) calc(var(--base-hue) - 30));
  
  /* 補色 */
  --color-complement: oklch(var(--lightness) var(--saturation) calc(var(--base-hue) + 180));
  
  /* トーンバリエーション */
  --color-light: color-mix(in oklch, var(--color-1) 60%, white);
  --color-dark: color-mix(in oklch, var(--color-1) 60%, black);
}

/* JavaScriptから動的に変更 */
.palette-generator {
  animation: hue-rotation 10s linear infinite;
}

@keyframes hue-rotation {
  to {
    --hue: 560; /* 360 + 200 */
  }
}

テーマバリエーション

/* ベーステーマ */
:root {
  --theme-primary: #3b82f6;
  --theme-secondary: #8b5cf6;
  --theme-accent: #ec4899;
}

/* 派生色の自動生成 */
.theme-surface {
  --surface-1: color-mix(in oklch, var(--theme-primary) 5%, white);
  --surface-2: color-mix(in oklch, var(--theme-primary) 10%, white);
  --surface-3: color-mix(in oklch, var(--theme-primary) 15%, white);
  
  background: var(--surface-1);
  border: 1px solid color-mix(in srgb, var(--theme-primary) 20%, transparent);
}

/* カスタムテーマの適用 */
[data-theme="ocean"] {
  --theme-primary: #0ea5e9;
  --theme-secondary: #06b6d4;
  --theme-accent: #14b8a6;
}

[data-theme="sunset"] {
  --theme-primary: #f59e0b;
  --theme-secondary: #ef4444;
  --theme-accent: #ec4899;
}

グラスモーフィズム効果

.glass-card {
  --glass-color: #ffffff;
  --glass-opacity: 15%;
  
  background: color-mix(
    in srgb,
    var(--glass-color) var(--glass-opacity),
    transparent
  );
  backdrop-filter: blur(12px) saturate(180%);
  border: 1px solid color-mix(
    in srgb,
    var(--glass-color) 30%,
    transparent
  );
  box-shadow: 
    0 8px 32px color-mix(in srgb, black 10%, transparent),
    inset 0 1px 0 color-mix(in srgb, white 20%, transparent);
}

/* ダークモード対応 */
@media (prefers-color-scheme: dark) {
  .glass-card {
    --glass-color: #1a1a1a;
    --glass-opacity: 40%;
  }
}

パフォーマンス最適化

CSS変数との組み合わせ

/* 計算結果をキャッシュ */
:root {
  --primary: #3b82f6;
  
  /* 一度だけ計算 */
  --primary-light: color-mix(in oklch, var(--primary) 80%, white);
  --primary-dark: color-mix(in oklch, var(--primary) 80%, black);
}

/* 再利用 */
.button {
  background: var(--primary);
}

.button:hover {
  background: var(--primary-light);
}

.button:active {
  background: var(--primary-dark);
}

カスケードの活用

/* コンテキストに応じた色の継承 */
.theme-context {
  --context-color: #3b82f6;
}

.theme-context .child {
  /* 親のコンテキストカラーを継承 */
  background: color-mix(in oklch, var(--context-color) 10%, white);
  border: 1px solid color-mix(in srgb, var(--context-color) 30%, transparent);
}

フォールバック対応

古いブラウザへの対応

.modern-color {
  /* フォールバック */
  background: #3b82f6;
  
  /* color-mix()をサポートしているブラウザのみ */
  background: color-mix(in oklch, #3b82f6 80%, white);
}

/* @supportsで分岐 */
@supports (background: color-mix(in oklch, red, blue)) {
  .advanced-colors {
    --primary: #3b82f6;
    background: color-mix(in oklch, var(--primary) 90%, white);
  }
}

@supports not (background: color-mix(in oklch, red, blue)) {
  .advanced-colors {
    /* 代替実装 */
    background: rgba(59, 130, 246, 0.9);
  }
}

ベストプラクティス

1. 適切なカラースペースの選択

  • OKLCH: 知覚的に均一な色変化が必要な場合(推奨)
  • sRGB: 単純な混合で十分な場合
  • HSL: 色相の変化を重視する場合
  • LAB: 科学的に正確な色表現が必要な場合

2. CSS変数との組み合わせ

/* 良い例: 再利用可能 */
:root {
  --brand: #3b82f6;
  --brand-light: color-mix(in oklch, var(--brand) 80%, white);
}

/* 悪い例: 重複した計算 */
.element1 {
  background: color-mix(in oklch, #3b82f6 80%, white);
}
.element2 {
  background: color-mix(in oklch, #3b82f6 80%, white);
}

3. アクセシビリティの確保

  • コントラスト比を常にチェック
  • 色だけに依存しない表現
  • 高コントラストモードへの対応

まとめ

color-mix()関数により:

  • 動的な色生成がCSSだけで可能に
  • デザインシステムの実装が簡潔に
  • ダークモード対応が容易に
  • アクセシビリティの向上

適切なカラースペース選択と、CSS変数との組み合わせにより、保守性の高いスタイルシートを構築できます。