GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

html カラーピッカー デザイン9選 — ホイールから履歴色まで

「html カラーピッカー デザイン」で検索すると、色を選ぶ見た目だけを真似た静止画像がほとんど出てきます。ここで扱う9つの画面はその手前に計算の層を1枚重ねます。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

デモ

9項目とも「見た目の演出ではなく、選択地点の角度・つまみの位置比率・グリッドの選択インデックス・キャンバス上のピクセル座標・色同士のユークリッド距離という値を毎フレーム求め直してハンドルの位置・トラックのグラデーション・枠線とチェックアイコンの色・統合後の色数に反映する」という同じ前提を共有しています。実測値は次のとおりです。

番号 項目 累積面積 強度平均 動いたフレーム
01 iOSホイール 8.494% 88.7 23/23
02 Materialスライダー 17.368% 72.3 23/23
03 Windowsスペクトラム 15.937% 38.1 23/23
04 macOSクレヨン 14.251% 56.6 23/23
05 Figmaパネル 10.797% 42.9 23/23
06 HSLスライダー 15.53% 44.5 23/23
07 スウォッチチップ 9.408% 116.1 7/23
08 スポイト 8.365% 98.7 23/23
09 履歴色 13.808% 45.6 23/23

02Materialスライダーが累積面積17.368%で最も広いのは、3つのつまみとバー・プレビューが同時に動くためです。08スポイトの8.365%が最も狭いのは、動くのがルーペとプレビューの枠だけで、背景のキャンバス自体は最初に一度描いた4本の帯のまま変わらないからです。強度平均は03Windowsスペクトラムが38.1で最も落ち着いており(正方形の中を滑らかに移動するため色の変化も緩やかです)、07スウォッチチップが116.1で最も高くなっています(SEG_MS400msごとに色が瞬時に切り替わるため画面差分が大きく出ます)。07が動いたフレーム7/23で最も少ないのも同じ理由で、6色の間を連続移動せず一定間隔で切り替える構造上の性質であり、欠陥ではありません(コンポーネントの精巧さ基準)。9項目とも下限(8%)は満たしており、最小の08でも8.365%とわずかに上回っています。実際のユーザー操作に反応するリスナーは0項目で、9項目全てrequestAnimationFrameによる固定周期の自動再生だけで示されます。

カラーピッカーUIの解剖 — 9つの画面が計算するもの

「カラーピッカー ui」で検索して出てくるもののほとんどは、色相環や四角形パッドの見た目だけを整えた静的な画像です。ここで扱う9つの画面は、その手前に計算の層を1枚重ねます。座標・時間経過・ピクセルの明るさ・色同士の距離というデータを毎回読み直し、ハンドルの位置・トラックのグラデーション・枠線の色・チェックアイコンの対比・統合後の色数に変換し直す設計です。

何が入力で何が出力か

入力は円形・正方形パッド上の座標、スライダーやグリッドの位置比率、キャンバス上のピクセル座標、色同士のユークリッド距離という4種類です。出力はCSS変数(--hueや--colsなど)か、DOMに直接書き込むスタイル・テキスト・aria属性のどちらかで、レイアウトのキーフレーム自体は変えません。

なぜ一枚の層に計算をまとめるのか

色空間の変換をあちこちに散らすと、LUM_THRESHOLDやMERGE_DISTのような定数を1つ変えるたびに複数箇所を直す必要が出ます。applyのような専用の関数に集めておけば、定数を差し替えるだけで表示全体のつじつまが揃います。自分のカラーピッカーを自作するときも、同じように計算をひとつの層にまとめておくと後から値を調整しやすくなります。

画面別コード

「カラーピッカー css」で探して来た方は、まずこの9項目の実装から見るのが早いです。同じ「スポイト」でも、カラーピッカー UI 9選の09は実際のEyeDropper APIで画面全体から色を拾いますが、この08はgetImageDataで自前のキャンバスだけを読み取ります。9項目とも実キー・実サーバーへの通信は一切なく、プラットフォームの名前(iOS・Material・Windows・macOS・Figma)はいずれも見た目の系統を指す説明的な参照で、各社の実際の仕様やバージョンを指すものではありません。

01iOSホイール — 選択地点の角度から色相を求め直す円形カラーピッカー

円形パッド上を巡る6地点の座標からMath.atan2で角度を求め直して色相にし、中心からの距離をRADIUS60に対する比率として彩度にも変換して、色相環に沿った円形カラーピッカーの見た目を毎フレーム描き直します。

iOSホイール — 選択地点の角度から色相を求め直す円形カラーピッカー
新しいタブで開く

01 · iOSホイール — 選択地点の角度から色相を求め直す円形カラーピッカー — 計測 累積面積 8.494% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

Math.atan2pointAtaria-valuetext

Math.atan2とpointAtが角度から色相を求め直す

pointAtは6つのWAYPOINTSの間を線形補間して現在の座標を返し、applyがその座標の角度と距離から色相・彩度を求め直してハンドルの位置とHEXの表示を同時に書き換えます。

  function apply(t) {
    var p = pointAt(t);
    var angle = Math.atan2(p.y, p.x);
    var hue = ((angle * 180 / Math.PI) + 360) % 360;
    var dist = Math.min(1, Math.hypot(p.x, p.y) / RADIUS);
    var sat = dist * 100;
    var rgb = hslToRgb(hue, sat, LIGHTNESS);
    var hex = '#' + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
    var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
    var ink = brightness > 150;

    handleEl.style.transform = 'translate(-50%, -50%) translate(' + p.x.toFixed(2) + 'px, ' + p.y.toFixed(2) + 'px)';
    handleEl.style.background = hex;
    readoutEl.style.background = hex;
    readoutEl.classList.toggle('is-ink', ink);
    hexEl.textContent = hex;
    wheelEl.setAttribute('aria-valuenow', Math.round(hue));
    wheelEl.setAttribute('aria-valuetext', hex);
  }

02Materialスライダー — 3つのつまみの位置を合わせてプレビュー色を計算するカラーピッカー

色相・彩度・明度3つのトラックの位置比率をvaluesAtで3区間に分けて求め直し、setThumbでそれぞれのつまみとバーの長さを同時に動かして、プレビュー色を毎フレーム合成し直すカラーピッカーです。

Materialスライダー — 3つのつまみの位置を合わせてプレビュー色を計算するカラーピッカー
新しいタブで開く

02 · Materialスライダー — 3つのつまみの位置を合わせてプレビュー色を計算するカラーピッカー — 計測 累積面積 17.368% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

valuesAtsetThumbaria-valuenow

valuesAtとsetThumbが3つのつまみの位置を合わせる

applyはvaluesAtが返した色相・彩度・明度をそれぞれsetThumbに渡してつまみとバーを同時に動かし、そこから求めた輝度が150を超えるとプレビューの文字色を暗く切り替えます。

  function apply(t) {
    var v = valuesAt(t);
    setThumb(thumbH, fillH, v.h / 360);
    setThumb(thumbS, fillS, v.s / 100);
    setThumb(thumbL, fillL, v.l / 100);

    var rgb = hslToRgb(v.h, v.s, v.l);
    var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
    previewEl.style.background = 'hsl(' + v.h.toFixed(1) + 'deg ' + v.s.toFixed(1) + '% ' + v.l.toFixed(1) + '%)';
    previewEl.classList.toggle('is-ink', brightness > 150);

    thumbH.setAttribute('aria-valuenow', Math.round(v.h));
    thumbS.setAttribute('aria-valuenow', Math.round(v.s));
    thumbL.setAttribute('aria-valuenow', Math.round(v.l));
  }

03Windowsスペクトラム — 2次元の位置を彩度・明度に分けて求めるカラーピッカー

正方形パッド上を巡る6地点の座標を彩度・明度に、円周に沿ったサイクル位置を色相に分けて求め直し、hsvToRgbで色を合成し直すカラーピッカーです。

Windowsスペクトラム — 2次元の位置を彩度・明度に分けて求めるカラーピッカー
新しいタブで開く

03 · Windowsスペクトラム — 2次元の位置を彩度・明度に分けて求めるカラーピッカー — 計測 累積面積 15.937% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

hsvToRgbpointAtaria-valuetext

hsvToRgbとpointAtが2次元の位置を彩度・明度に分ける

pointAtが返す0から1の座標をそのまま彩度と明度の比率として使い、hsvToRgbがその2値と色相を合わせてHEXを求め直してマーカーの位置とスウォッチの背景を同時に書き換えます。

  function apply(t) {
    var p = (t / CYCLE_MS) % 1;
    var pt = pointAt(t);
    var hue = p * 360;
    var sat = pt.x * 100;
    var val = (1 - pt.y) * 100;

    var rgb = hsvToRgb(hue, sat, val);
    var hex = '#' + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
    var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
    var ink = brightness > 150;

    squareEl.style.setProperty('--hue', hue.toFixed(1));
    squareMarkerEl.style.transform = 'translate(-50%, -50%) translate(' + (pt.x * SQUARE_W).toFixed(2) + 'px, ' + (pt.y * SQUARE_H).toFixed(2) + 'px)';
    hueMarkerEl.style.transform = 'translate(-50%, -50%) translateY(' + ((hue / 360) * STRIP_H).toFixed(2) + 'px)';

    swatchEl.style.background = hex;
    readoutEl.style.background = hex;
    readoutEl.classList.toggle('is-ink', ink);
    hexEl.textContent = hex;

    squareEl.setAttribute('aria-valuenow', String(Math.round(val)));
    squareEl.setAttribute('aria-valuetext', hex);
    hueStripEl.setAttribute('aria-valuenow', String(Math.round(hue)));
  }

04macOSクレヨン — 明るさの値で枠線の色を決めるグリッド型カラーピッカー

9色のCRAYONSグリッドの中心座標をcenterOfで求め直し、選択中の色の明るさがLUM_THRESHOLD .6を下回ると白い枠線に、上回ると黒い枠線に選択リングの色を切り替えるカラーピッカーです。

macOSクレヨン — 明るさの値で枠線の色を決めるグリッド型カラーピッカー
新しいタブで開く

04 · macOSクレヨン — 明るさの値で枠線の色を決めるグリッド型カラーピッカー — 計測 累積面積 14.251% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

role=radiogroupLUM_THRESHOLDcenterOf

LUM_THRESHOLD .6がcenterOfで求めた明るさから枠線の色を決める

applyは選択中のクレヨンのRGB値から明るさを求め、その値がLUM_THRESHOLDを下回ればis-white-ringを、上回ればis-black-ringを選択リングに付け替えて、9マス全てのaria-checkedも同時に更新します。

  function apply(t) {
    var idx = Math.min(ORDER.length - 2, Math.floor(t / SEG_MS));
    var frac = (t % SEG_MS) / SEG_MS;
    var a = centerOf(ORDER[idx]);
    var b = centerOf(ORDER[idx + 1]);
    var x = a.x + (b.x - a.x) * frac;
    var y = a.y + (b.y - a.y) * frac;
    ringEl.style.transform = 'translate(' + x.toFixed(2) + 'px, ' + y.toFixed(2) + 'px)';

    var selIndex = ORDER[frac < .5 ? idx : idx + 1];
    var c = CRAYONS[selIndex];
    var rgb = hslToRgb(c.h, c.s, c.l);
    var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000 / 255;
    var white = brightness < LUM_THRESHOLD;
    ringEl.classList.toggle('is-white-ring', white);
    ringEl.classList.toggle('is-black-ring', !white);

    var hex = crayonEls[selIndex].dataset.hex;
    swatchEl.style.background = hex;
    hexEl.textContent = hex;

    for (var i = 0; i < crayonEls.length; i++) {
      crayonEls[i].setAttribute('aria-checked', i === selIndex ? 'true' : 'false');
    }
  }

05Figmaパネル — 3つの表記を互いに合わせて求め直すカラーピッカー

PRESETS7色の間を巡回しながら同じ色をHEX・RGB・HSLの3つの表記へ毎フレーム求め直し、3つのフィールドが常に同じ色を指すよう合わせ直すカラーピッカーです。

Figmaパネル — 3つの表記を互いに合わせて求め直すカラーピッカー
新しいタブで開く

05 · Figmaパネル — 3つの表記を互いに合わせて求め直すカラーピッカー — 計測 累積面積 10.797% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

hexFieldrgbFieldPRESETS

PRESETS7色とhexField・rgbFieldが表記を合わせる

applyは7色の間を線形補間した色相・彩度・明度からhslToRgbでRGBを求め、そのRGBをHEX・RGB・HSLの3つの文字列に変換してhexField・rgbField・hslFieldへ同時に書き込みます。

  function apply(t) {
    var idx = Math.min(PRESETS.length - 2, Math.floor(t / SEG_MS));
    var frac = (t % SEG_MS) / SEG_MS;
    var a = PRESETS[idx];
    var b = PRESETS[idx + 1];
    var h = a.h + (b.h - a.h) * frac;
    var s = a.s + (b.s - a.s) * frac;
    var l = a.l + (b.l - a.l) * frac;
    var hMod = h % 360;

    var rgb = hslToRgb(h, s, l);
    var hex = '#' + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
    var hslStr = 'hsl(' + hMod.toFixed(0) + 'deg ' + s.toFixed(0) + '% ' + l.toFixed(0) + '%)';
    var rgbStr = 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';

    swatchEl.style.background = hslStr;
    hexField.textContent = hex;
    rgbField.textContent = rgbStr;
    hslField.textContent = hslStr;
  }

06HSLスライダー — 色相の値に応じてトラックのグラデーションを描き直すカラーピッカー

色相の値をGRADIENT_STEPS20段階に分けてbuildGradientでトラックのグラデーションを描き直し、色相・彩度・明度3つのつまみの位置とプレビュー色を毎フレーム合わせ直すカラーピッカーです。

HSLスライダー — 色相の値に応じてトラックのグラデーションを描き直すカラーピッカー
新しいタブで開く

06 · HSLスライダー — 色相の値に応じてトラックのグラデーションを描き直すカラーピッカー — 計測 累積面積 15.53% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

hslbuildGradientGRADIENT_STEPS

GRADIENT_STEPS20がbuildGradientでトラックの階調を決める

buildGradientはGRADIENT_STEPS回のループでグラデーションの色止まりを積み上げてlinear-gradientの文字列を作り、applyが色相が変わるたびに彩度・明度それぞれのトラックをこの関数で描き直します。

  function apply(t) {
    var p = t / CYCLE_MS;
    var hue = (p * 360) % 360;
    var sat = 55 + Math.sin(p * Math.PI * 2 * 1.3) * 35;
    var light = 50 + Math.sin(p * Math.PI * 2 * .9 + 1) * 25;

    setThumb(hThumb, hue / 360);
    setThumb(sThumb, sat / 100);
    setThumb(lThumb, light / 100);

    sTrack.style.background = buildGradient(function (r) {
      return 'hsl(' + hue.toFixed(1) + 'deg ' + (r * 100).toFixed(1) + '% 50%)';
    });
    lTrack.style.background = buildGradient(function (r) {
      return 'hsl(' + hue.toFixed(1) + 'deg ' + sat.toFixed(1) + '% ' + (r * 100).toFixed(1) + '%)';
    });
    previewEl.style.background = 'hsl(' + hue.toFixed(1) + 'deg ' + sat.toFixed(1) + '% ' + light.toFixed(1) + '%)';

    hThumb.setAttribute('aria-valuenow', String(Math.round(hue)));
    sThumb.setAttribute('aria-valuenow', String(Math.round(sat)));
    lThumb.setAttribute('aria-valuenow', String(Math.round(light)));
  }

07スウォッチチップ — 明るさの値でチェックアイコンの対比を合わせるカラーピッカー

SEG_MS400msごとにSWATCHES6色の間を離散的に切り替え、選択中の色の明るさが150を超えるとチェックアイコンを暗く、下回ると明るく切り替えてチップとの対比を合わせ直すカラーピッカーです。

スウォッチチップ — 明るさの値でチェックアイコンの対比を合わせるカラーピッカー
新しいタブで開く

07 · スウォッチチップ — 明るさの値でチェックアイコンの対比を合わせるカラーピッカー — 計測 累積面積 9.408% · 動いたフレーム 7/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

role=radiogrouparia-checkedSWATCHES

SWATCHES6色とaria-checkedがチェックアイコンの対比を合わせる

applyは6つのチップそれぞれの明るさをforループで調べてis-inkの付け替えとaria-checkedの更新を同時に行い、選択中のチップだけ読み出し欄の背景色も合わせて書き換えます。

  function apply(t) {
    var idx = Math.min(ORDER.length - 2, Math.floor(t / SEG_MS));
    var frac = (t % SEG_MS) / SEG_MS;
    var selIndex = ORDER[frac < .5 ? idx : idx + 1];

    for (var i = 0; i < chipEls.length; i++) {
      var isSel = i === selIndex;
      var rgb = HEX[i].rgb;
      var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
      var ink = brightness > 150;
      chipEls[i].classList.toggle('is-selected', isSel);
      chipEls[i].classList.toggle('is-ink', ink);
      chipEls[i].setAttribute('aria-checked', isSel ? 'true' : 'false');
    }

    hexEl.textContent = HEX[selIndex].hex;
    readoutEl.style.background = chipEls[selIndex].style.background;
    var selBrightness = (HEX[selIndex].rgb[0] * 299 + HEX[selIndex].rgb[1] * 587 + HEX[selIndex].rgb[2] * 114) / 1000;
    readoutEl.classList.toggle('is-ink', selBrightness > 150);
  }

08スポイト — 抽出された色の明るさでプレビューを求め直すカラーピッカー

三角波の軌跡でキャンバス上を移動するルーペの座標からgetImageDataで1ピクセルの色を実際に読み取り、その明るさに応じてプレビューの枠の太さと文字色を求め直すカラーピッカーです。

スポイト — 抽出された色の明るさでプレビューを求め直すカラーピッカー
新しいタブで開く

08 · スポイト — 抽出された色の明るさでプレビューを求め直すカラーピッカー — 計測 累積面積 8.365% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

getImageDataBANDSaria-hidden

getImageDataが抽出したピクセルの明るさでプレビューを求め直す

applyはctx.getImageDataが返したピクセルのRGB値から明るさを求め、その値をMIN_BORDER1からMAX_BORDER6までの範囲に写してプレビューの枠の太さに、150を超えるかどうかを文字色の切り替えにそれぞれ使います。

  function apply(t) {
    var p = (t / CYCLE_MS) % 1;
    var triangle = p < .5 ? p * 2 : (1 - p) * 2;
    var x = 8 + triangle * (CANVAS_W - 16);
    var y = CANVAS_H / 2 + Math.sin(p * Math.PI * 4) * (CANVAS_H / 2 - 6);
    var xi = Math.max(0, Math.min(CANVAS_W - 1, Math.round(x)));
    var yi = Math.max(0, Math.min(CANVAS_H - 1, Math.round(y)));

    var pixel = ctx.getImageData(xi, yi, 1, 1).data;
    var r = pixel[0], g = pixel[1], b = pixel[2];
    var hex = '#' + toHex(r) + toHex(g) + toHex(b);
    var brightness = (r * 299 + g * 587 + b * 114) / 1000;
    var ink = brightness > 150;
    var borderPx = MIN_BORDER + (brightness / 255) * (MAX_BORDER - MIN_BORDER);

    loupeEl.style.transform = 'translate(-50%, -50%) translate(' + x.toFixed(2) + 'px, ' + y.toFixed(2) + 'px)';
    previewEl.style.background = hex;
    previewEl.style.borderWidth = borderPx.toFixed(1) + 'px';
    previewEl.classList.toggle('is-ink', ink);
    hexEl.textContent = hex;
  }

09履歴色 — 色同士の距離を計算し近い色をまとめるリスト型カラーピッカー

10色のRAW配列それぞれの間のユークリッド距離をdistで計算し、その距離がMERGE_DIST30より近ければ同じグループにまとめて、統合後は10色から7色になったことを表示するカラーピッカーです。

履歴色 — 色同士の距離を計算し近い色をまとめるリスト型カラーピッカー
新しいタブで開く

09 · 履歴色 — 色同士の距離を計算し近い色をまとめるリスト型カラーピッカー — 計測 累積面積 13.808% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

distMERGE_DISThslToRgb

distとMERGE_DIST30が色同士の距離を測り近い色をまとめる

先頭から順に既に採用した色との距離をkept配列の中で調べ、MERGE_DISTより近い色が見つかった時点でその色のグループに合流させるため、RAW.length10とkept.length7を使って「10個 → 7個」という表示が一度だけ求められます。

  function dist(a, b) {
    return Math.sqrt(
      Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)
    );
  }

  var RGB = RAW.map(function (c) { return hslToRgb(c.h, c.s, c.l); });

  var kept = [];
  var groupOf = new Array(RAW.length);
  for (var i = 0; i < RAW.length; i++) {
    var found = -1;
    for (var j = 0; j < kept.length; j++) {
      if (dist(RGB[i], RGB[kept[j]]) < MERGE_DIST) { found = kept[j]; break; }
    }
    if (found === -1) {
      kept.push(i);
      groupOf[i] = i;
    } else {
      groupOf[i] = found;
    }
  }

どこで使うか — ブランドカラーの編集画面と商品オプション画面

デザインシステムを管理する担当者がカラーピッカーを探すのは、たいてい既に決まったブランドカラーのパレットを編集画面で触れるようにしたい場面です。01のようにリング状のホイールで角度を操作させるか、05のように3つの表記を1画面で揃えて見せるかは、その編集画面を使うのがデザイナーなのか開発者なのかで変わります。

商品オプション画面を作るチームは別の課題を抱えています。09の履歴色一覧のように、直前に選んだ色をもう一度呼び出せるようにしたいものの、わずかに違う色まで全部残すと一覧が際限なく伸びてしまいます。距離の基準をどこに置いて似た色をまとめるかが、この画面の核心です。

どちらの場面でも、9つ全部を一度に組み込むよりも、必要な計算を1つか2つだけ取り出して使いたいことが多くあります。角度・明るさ・距離のうちどの計算が実際に必要かを先に見極めることが、どちらの場合も最初の一歩になります。

数値が画面を変える場所 — 角度・明るさ・距離

角度が色相を動かす場所

01iOSホイールでは、ハンドルの座標という2つの数値がMath.atan2で1つの角度に変わり、その角度がそのまま色相という1つの数値になってhslToRgbに渡されます。ハンドルの座標を直接色に書くのではなく、角度という中間値を経由している点がポイントです。

明るさが枠線とチェックアイコンの色を動かす場所

04macOSクレヨンと07スウォッチチップでは、選択中の色のRGB値という3つの数値が明るさという1つの数値に変わり、その明るさがLUM_THRESHOLDや150という閾値をまたいだ瞬間だけ枠線やチェックアイコンの色という2値の状態を切り替えます。連続した数値が離散した状態を1回だけ変える、という関係です。

距離が統合グループを動かす場所

09履歴色では、2色のRGB値という6つの数値がdistで1つの距離に変わり、その距離がMERGE_DIST30を下回るかどうかで、10個あった色が7個のグループへ統合されるかどうかが決まります。

3つのバリエーションとアクセシビリティ

紙のポスターに9色の見本を並べるのと違い、この9種は同じ見た目のまま定数を1つ変えるだけで体験の重さが変わります。9種のカラーピッカーはすべて59xbym8tで開くzipにまとめられており、vanillaフォルダには9項目それぞれのindex.htmlが、reactフォルダには同じ計算をコンポーネント化したtsxファイルが入っています。

名前 値 感じ方
クレヨンの明るさ閾値 04 LUM_THRESHOLD .5 → .6 閾値を.5にすると中間の明るさの色の近くで枠線の色が頻繁に入れ替わって落ち着かなく見え、.6に上げると入れ替わる地点が明るい側へ移り、暗い色のグループでは白い枠線がより長く保たれます
履歴色の統合距離 09 MERGE_DIST 10 → 30 統合距離を10にすると微妙に違う色まですべて別々に残って一覧が長くなり、30に上げると似た色同士がよくまとまって一覧は短くなりますが、実際には違う色が1つにまとめられてしまう危険が大きくなります
HSLスライダーのグラデーション段数 06 GRADIENT_STEPS 6 → 20 段数を6にするとトラックのグラデーションが階段状にかくかく見えますが描き直すコストは小さく、20に上げると色は滑らかにつながりますが色相が変わるたびに計算し直す値が増えます

prefers-reduced-motionをどう扱うか

9項目ともwindow.matchMedia('(prefers-reduced-motion: reduce)')の結果をreduceという変数に持ち、真であればapply(0)を1回呼ぶだけで自動再生のrAFループ自体は回しません。09履歴色はこの1回のapply(0)でもRAWの10色すべてがそれぞれの本来の格子位置にとどまり、色を統合して重ねる演出だけが止まった状態になります。aria-valuenow・aria-valuetext・aria-checkedのような属性はアニメーションの有無に関わらず常に実際の状態を反映します。

コピー&ペーストで動く理由

9項目とも定数・関数・クラス名を1つのindex.htmlの中だけで完結させ、外部のライブラリやCDNは一切呼び出しません。フォルダを開いてブラウザに読み込ませるだけで、追加のビルド手順なしに同じ計算がそのまま動き出します。自分のカラーピッカーを自作する際も、名前付き定数の値だけ差し替えれば同じ計算の層をそのまま流用できます。

極端な色と連続した重複選択、どこで壊れるか

明度が0に近い極端な色で、色相の指定が意味を失うとき

03WindowsスペクトラムのhsvToRgbは明度(val)が0に近づくと、色相(hue)にどんな値を渡してもRGBはほぼ(0, 0, 0)の黒に収束します。正方形パッドの下端に近づくほど色相環のどの角度を指していても見た目の違いがなくなるということで、色相という数値が常に意味を持つとは限らない例です。

ORDER配列の始点と終点が重なり、連続して同じ色が選ばれ続けるとき

04macOSクレヨンのORDERは先頭0から末尾でもう一度0へ戻る並びのため、サイクルの終わり際と始まり際の両方で同じクレヨン0が選ばれ続ける区間が生まれます。centerOf(ORDER[idx])とcenterOf(ORDER[idx+1])が同じ地点を指す間はリングの座標も動かないので、8色分の移動の後にもう一度クレヨン0で足踏みしてから折り返す、という見え方になります。

統合された色のopacityが、0にはならないとき

09履歴色の統合演出ではopacity = 1 - .9 * mergeAmountという式でチップを薄くしますが、mergeAmountは最大でも1までしか大きくならないため、opacityの最小値は0.1で止まり完全な透明(0)には一度もなりません。「html カラーピッカー 透明度」のような検索でアルファ値そのものを扱いたい場合、この9種にはRGBAやHSLAのアルファチャンネルを操作する項目がないため、統合済みの色が完全に消えることはなく薄くなった状態で重なって見え続けます。

埋め込む前によくある質問

9つのカラーピッカーはコピペだけで動きますか

はい。9項目とも定数・関数を1つのindex.htmlの中に完結させてあり、外部のライブラリやCDNを呼び出していないため、フォルダごと開くだけで同じ計算がそのまま動きます。

html カラーピッカーに本物のスポイト機能は使われていますか

いいえ。08スポイトはEyeDropperAPIを呼ばず、あらかじめキャンバスに描いた4本の帯の上を移動するルーペの座標からgetImageDataでピクセルを読み取るだけの、自前のキャンバスに限定したシミュレーションです。

css カラーピッカー 表示 の見た目だけを変えたい場合、どこを直せばいいですか

色や大きさのような見た目はCSS側の変数・クラスだけで調整でき、apply関数の中にある色相・彩度・明度や距離の計算式には触れる必要がありません。06HSLスライダーならGRADIENT_STEPSを増減して見た目の滑らかさだけを変える、というように計算とスタイルの層が分かれています。

もっと読む

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。