🐸

OKLCHで高コントラストなカラーパレットを生成するサイトを作った【ダークテーマ対応】

2024/10/02に公開

おいカラーパレットさ作るんだで!(A11y工船)

リスペクト

この記事およびプロダクトは、フロントエンドカンファレンス北海道での発表 「ダークテーマとアクセシビリティの融合したカラートークンの設計」 に感銘を受け製作しました。

https://speakerdeck.com/degudegu2510/dakutematoakusesibiriteinorong-he-sitakaratokunnoshe-ji

モチベーション

カラーパレットの明度変化について

ダークテーマを含めてコントラスト比を維持したカラーパレットを作るの、むずすぎひんか!!


出口 裕貴(Qiita.inc),「ダークテーマとアクセシビリティの融合したカラートークンの設計」August 23, 2024, slide 23

この問題に対して、上記スライドでは以下のような解決をしています。


出口 裕貴(Qiita.inc),「ダークテーマとアクセシビリティの融合したカラートークンの設計」August 23, 2024, slide 36

明度の変化曲線を工夫することで、見事にコントラスト比を高く維持することに成功しています。
天才すぎる...

OKLCHとの出会い

HSB色空間では、色相(Hue)、彩度(Saturation)、明度(Brightness)を変化させて色を作ることができますが、明度が同じでも、色相によって色への感じ方に違いが出てしまったりしまいます。

https://qiita.com/soi/items/9439ba59cef99b1a1ea5

そこで使えるのがOKLCH色空間です。
試せるサイトがあるのですが、一体これどういう色空間の設計してるんや...

https://oklch.com/#70,0.1,248,100

相対カラー構文

rgb(from green r g b)のように、fromから元の色を選択し、その後に各パラメータを入力すると、元の色からある一つのパラメータを変えた色を生成することができます。

https://coliss.com/articles/build-websites/operation/css/relative-color-syntax.html

これはつまり、oklch(from ${color} ${lightness} c h)のようにすれば、OKLCH空間を使って明度だけを変えた色を作れるということです!

作るしかない

カラーパレットを生成できるサイトは多くありますが、コントラスト比を気にしてくれるところはほぼありませんし、ダークテーマ対応となれば余計にありません。
企業のデザインシステムを見ても、そのあたりについて細かく言及しているものは多くなかったりします。少し前にゴールドマンサックスのデザインシステムはoklchの要素を取り入れて云々な話を聞きましたが、もう公開はされていないようです。

明度変化の扱い方や、OKLCH、相対カラー構文を見て、これカラーパレットを自動生成するサイト作れんじゃね? となったので作りました。

サイトの紹介

https://contrast-color-pallet.vercel.app

https://github.com/imaimai17468/contrast-color-pallet

機能は以下の通りです

  • 色の変更
  • カラー数の変更
  • 新しいパレットの追加
  • パレットの項目の表示/非表示
  • 背景色の変更
  • 明度の遷移の確認
  • 各色と背景色のコントラスト比の確認
  • URLコピーによる共有

ドメイン的な実装紹介

明度の計算

明度の計算については、シグモイド関数をxが0~10の範囲で、最小値0.1になるように変形したものを用いました。

実際のコードとしては以下のように実装しました。

sigmoid.ts
const sigmoid = (x: number): number => {
  const k = 10; // x軸の範囲(0〜10)に対応するスケーリングファクター
  const rawSigmoid = 1 / (1 + Math.exp(-0.5 * (x - k / 2)));

  // 値を 0.1 ~ 1 にスケーリングする
  return 0.1 + 0.9 * rawSigmoid;
};

export const generateSigmoidData = (steps: number): { x: number; y: number }[] => {
  const start = 0;
  const end = 10;
  const stepSize = (end - start) / (steps - 1);
  return Array.from({ length: steps }, (_, i) => {
    const x = start + i * stepSize;
    const y = sigmoid(x);
    return { x, y };
  });
};

カラーパレットの生成

相対カラー構文の話をしたと思いますが、コピペしやすくするために、カラーコードに直さなくてはいけないということで、こちらは使用しないことになりました。(最初は使ってた)
色周りの処理に関しては、culoriというライブラリを使って相互変換しています。

https://culorijs.org/

以下にカラーコードと明度を投げると、oklch色空間で生成した色をまたカラーコードで返す関数を示します。

culcHexColor.tsx
import { type Oklch, formatHex, formatHex8, oklch, parse } from "culori";

export const culcHexColor = (color: string, lightness: number) => {
  const parsedColor = parse(color);
  const oklchColor = oklch(parsedColor) as Oklch;
  const adjustedColor = oklch({
    mode: "oklch",
    l: lightness,
    c: oklchColor.c,
    h: oklchColor.h,
    alpha: oklchColor.alpha,
  });
  return adjustedColor.alpha ? formatHex8(adjustedColor) : formatHex(adjustedColor);
};

その他技術構成

  • Next.js (AppRouter)
  • shadcn/ui
  • nuqs

nuqs

最近個人的にアツいライブラリです。
クエリパラメータとstateの連携がしやすくなります。
作ったパレットを他に人に共有できた方がいいと思い、こちらを使ってクエリパラメータ祭りにしました。以前私がLT会で使った資料も貼っておきます。

https://nuqs.47ng.com/

https://speakerdeck.com/imaimai17468/next-dot-jsdekueriparametawole-nixi-ou-nuqswoshao-jie

終わりに

追加して欲しい機能・修正して欲しい機能があれば気軽にTwitterなりで投げつけてください!
どちらかというとデザイナーさんが便利になると思うので、エンジニアの方が見ていらしたら、周りのデザイナーさんに教えていただけると助かります。

株式会社ゆめみ

Discussion