読者です 読者をやめる 読者になる 読者になる

はしくれエンジニアもどきのメモ

情報・Web系技術の勉強メモ・備忘録です。

CSS の色指定は、HSL を使っていこう

CSS の色指定は、HSL を使っていこう

色の勉強をしていると、 色の3属性として 色相 (Hue)・彩度 (Saturation)・明度 (Lightness)が出てくる。 しかし、RGBで色を指定しまう(指定する機会が多い)と、 この3属性を活かすことができない。。 hsl(); を使えば、色の3属性そのままで、色を指定することができる。

HSL の利点として、 彩度 (S)と明度 (L)を固定にして、色相 (Hue)を変えるだけで、 同じトーンの色を作ることができる。 さらに、彩度 (S)と色相 (H)を固定にして、 明度 (L)を変えることで、明るさの違う同系色を作ることができる。 色弱の方でも、明度の違いがわかる方は多いので、 アクセシビリティに強い配色を行うことができる。

ColorGenerator(色変換)サンプル

ES6 のclass を使っているので、最新版Chrome で動作します。 range をグリグリして、色を決めることができます。

RGBtoHSL HSLtoRGB

トーン、同系色サンプル

同じトーンの色、同系色の例を貼っておきます。 codepen.io

hsl対応ブラウザ

対応状況としては、 IE8以前には対応しておらず、 IE9以降のブラウザで対応しています。

Can I use... Support tables for HTML5, CSS3, etc

HSL to RGB

HSL からRGB への変換アルゴリズムは、 W3C の CSS Color Module Level 3 ( 日本語訳: CSS カラーモジュール Level 3)

に書いてあります。

以下に、ABC記法で書かれたHSL to RGB への変換アルゴリズムを 引用して貼っておきます。


HOW TO RETURN hsl.to.rgb(h, s, l):
 SELECT:
  l<=0.5: PUT l*(s+1) IN m2
  ELSE: PUT l+s-l*s IN m2
 PUT l*2-m2 IN m1
 PUT hue.to.rgb(m1, m2, h+1/3) IN r
 PUT hue.to.rgb(m1, m2, h    ) IN g
 PUT hue.to.rgb(m1, m2, h-1/3) IN b
 RETURN (r, g, b)

HOW TO RETURN hue.to.rgb(m1, m2, h): 
 IF h<0: put="" h="" 1="" in="" if="">1: PUT h-1 IN h
 IF h*6<1: return="" m1="" m2-m1="" h="" 6="" if="" 2="" 1:="" m2="" 3="" 2:="" 3-h="" code="">

これをJS で書いたものを貼っておきます。


function hsl2rgb(h_deg, s_percent, l_percent) {
  var h_norm = h_deg / 360;
  var s_norm = s_percent / 100;
  var l_norm = l_percent / 100;

  var m2;
  if (l_norm < 0.5) {
    m2 = l_norm * (s_norm + 1);
  } else {
    m2 = l_norm + s_norm - l_norm * s_norm;
  }

  var m1 = l_norm * 2 - m2;
  var r_norm = _hsl2rgb(m1, m2, h_norm + 1 / 3);
  var g_norm = _hsl2rgb(m1, m2, h_norm);
  var b_norm = _hsl2rgb(m1, m2, h_norm - 1 / 3);

  var r = parseInt(Math.round(r_norm * 255));
  var g = parseInt(Math.round(g_norm * 255));
  var b = parseInt(Math.round(b_norm * 255));

  return [r, g, b];
}


function _hsl2rgb(m1, m2, h) {
  var _h = h;
  if (_h < 0) {
    _h = _h + 1;
  }
  if (_h > 1) {
    _h = _h - 1;
  }

  if (_h * 6 < 1) {
    return m1 + (m2 - m1) * _h * 6;
  }
  if (_h * 2 < 1) {
    return m2;
  }
  if (_h * 3 < 2) {
    return m1 + (m2 - m1) * (2 / 3 - _h) * 6;
  }

  return m1;
}

RGB to HSL

RGB からHSL への変換アルゴリズムは、 W3Cの仕様 CSS Color Module Level 3 には書いてないです。

なので、以下を参考にしました。

www.peko-step.com

詳細は省きますが、JS で書いたコードを貼っておきます。


function rgb2hsl(r, g, b) {
  var h = _calcHue(r, g, b);
  var s = _calcSaturation(r, g, b);
  var l = _calcLightness(r, g, b);
  return [h, s, l]
}

色相(H)の計算では、H = 0 (0[deg]red)~1 (360[deg]red)に収めるよう計算します。 H = 1/3 (120[deg])であれば green となり、 H = 2/3 (240[deg])であれば、blue となります。


function _calcHueNorm(r, g, b) {
  var color_arr = [r, g, b];
  var h_norm = 0.0;

  // 等しいとき
  if ((color_arr[0] == color_arr[1]) && (color_arr[1] == color_arr[2])) {
    h_norm = 0.0;
  } else if ((color_arr[0] >= color_arr[1]) && (color_arr[0] >= color_arr[2])) {
    // r最大の場合
    h_norm = ((color_arr[1] - color_arr[2]) / (Math.max.apply(null, color_arr) - Math.min.apply(null, color_arr))) / 6;
  } else if ((color_arr[1] >= color_arr[0]) && (color_arr[1] >= color_arr[2])) {
    // g最大の場合
    h_norm = ((color_arr[2] - color_arr[1]) / (Math.max.apply(null, color_arr) - Math.min.apply(null, color_arr))) / 6 + 1 / 3;
  } else if ((color_arr[2] >= color_arr[0]) && (color_arr[2] >= color_arr[1])) {
    // b最大の場合
    h_norm = ((color_arr[0] - color_arr[1]) / (Math.max.apply(null, color_arr) - Math.min.apply(null, color_arr))) / 6 + 2 / 3;
  }

  // 求めたhueが 0以下なら1足す
  if (h_norm < 0) {
    h_norm += 1;
  }
  return h_norm;
}

function _calcHue(r, g, b) {
  return Math.round(this._calcHueNorm(r, g, b) * 360);
}

明度(L)(輝度)の計算では、 RGBのうち最大値と最小値を求め、その平均になります。 red(rgb(255, 0, 0)), green(rgb(0, 255, 0)), blue(rgb(0, 0, 255)) では、明度は、等しく128(50%) になります。


function _calcLightnessNorm(r, g, b) {
  var color = [r, g, b];
  var max = Math.max.apply(null, color);
  var min = Math.min.apply(null, color);
  var l = (max + min) / 2;
  l /= 255;
  return l;
}

function _calcLightness(r, g, b) {
  return Math.round(this._calcLightnessNorm(r, g, b) * 100);
}

彩度(S): 0%というのはRGBがすべて同じで、 グレースケールの世界なので明度の値のとき、S = 0% になる。 一方、彩度: 100%は、彩度0%のときのRGBが均等に拡がって、いずれかが0 または255になったときが S = 100% になる。 なので、輝度(S=0%)とS=100%になるまでの幅と現在値がわかれば、現在の彩度(S)を求めることができる。


function _calcSaturationNorm(r, g, b) {
  var color = [r, g, b];

  // 収束値CNTを求める
  var max = Math.max.apply(null, color);
  var min = Math.min.apply(null, color);
  var cnt = (max + min) / 2;

  var s = 0.0;
  if (cnt < 127) {
    s = (max - min) / (max + min);
  } else if (510 <= (max + min)) {
    return 0.0;
  } else {
    s = (max - min) / (510 - max - min);
  }
  return s;
}

function _calcSaturation(r, g, b) {
  return Math.round(this._calcSaturationNorm(r, g, b) * 100);
}

RGB(10進) to RGB(16進)

RGB10進数からRGB16進数に変換する関数も貼っておきます。

parseInt(hoge).toString(16) で16進数の文字列へ 変換できます。しかし、00 - 0f にはならず、1桁になってしまうので、 .replace(/^([0-9]|[a-f])$/, '0$&')で、'0'を 追加するようにしています。


function toHEX() {
  return '#' +
  parseInt(this.r_).toString(16).replace(/^([0-9]|[a-f])$/, '0$&') +
  parseInt(this.g_).toString(16).replace(/^([0-9]|[a-f])$/, '0$&') +
  parseInt(this.b_).toString(16).replace(/^([0-9]|[a-f])$/, '0$&');
}

自作Color class コード

上のサンプルで使った、rgb2hsl, hsl2rgb で計算してくれる Color class を貼っておきます。

rgb2hsl, hsl2rgbの色変換