コードによる着色:デザインへのプログラム的アプローチ

Coloring with Code

色は、デザインの印象を大きく左右する重要な要素です。

組み合わせに成功すれば、デザインを想定以上の出来栄えにできますが、色を間違ってしまうとデザインを台無しにしてしまう危険性もあります。だからこそ、色選びで悩んでいるデザイナーは多いでしょう。

今回は、テキストエディタとWebブラウザという、プログラマーにとって使い慣れたツールを使って、印象的なカラーパレットをプログラム的に作る方法をご紹介します。

はじめに

前提知識と対象読者について

この記事は、HTML、CSS、JavaScriptの知識があると、理解しやすい内容になっています。

くわえてHSL(色相/彩度/輝度)RGB(レッド/グリーン/ブルー)の知識があると、なお理解しやすいはずです。

Web上での創作活動に取り組むのが好きで、あらかじめ用意されている色や自動生成ツールを活用しているなら、この記事を参考にしてみてください。

チュートリアルフォーマット

本記事では、厳密に定義されたひとつのプロジェクトに取り組むわけではありません。

そのかわりに、美しいカラーパレットを作るのに最適な、3つのJavaScript関数の作りかたをご紹介します。

今回ご紹介する関数は、一度書けばカラーツールの基礎になります。複数のプロジェクトで繰り返し使用したり、カスタマイズしたりできるでしょう。

LCHカラーとは

今回のチュートリアルでは、ほぼLCHカラーのみを使用します。LCHとは、Light(明度)/Chroma(彩度)/Hue(色相)の頭文字を取ったもの。RGBやHSLと同様、色を表現する方法のひとつです。

RGBやHSLとの違いは、知覚的均一性と明度にあります。すこし難しい概念に思えるかもしれないので、ここからは図を使って解説します。

知覚的均一性の違い

まず、この上下2組のHSLカラーを見てみましょう。

Coloring with Code

▲出典:codrops

上の2色と下の2色のペアにおける色相差は、20と40、230と250で、どちらも20です。しかし、上の2色よりも、下の2色のほうが色が似たように見えますよね。

この違いの原因になっているのが、さきほど述べた、知覚的均一性です。HSLでは知覚的均一性が低いため、色相差が同じでも、色に差が生じてしまいます。

では、つぎにLCHカラーを使って同じ実験をしてみましょう。

Coloring with Code

▲出典:codrops

HSLカラーと同様に、色相差を20にしました。色相の値は、HSLとLCHで完全に一致するわけではありません。しかしHSLに比べると、上2色と下2色のペアは、同じように色が変化しています。

LCHは知覚的均一性が高いため、色相が変化してもバランスが取れるのです。

明度の違い

つぎに、2種類の異なるHSLカラーを見てみましょう。

Coloring with Code

▲出典:codrops

この2色の明度は同じですが、左の黄色は、右の青より、はるかに明るく見えるはずです。

では、同じような設定のLCHカラーを見てみましょう。

Coloring with Code

▲出典:codrops

LCHカラーでは、明度が同じだと、同程度の明るさに見えます。

このように、LCHの均一な色相分布は、調和のとれたカラーパレットを作るときに、役立ちます。

チュートリアルではライブラリを使用しますが、LCHのネイティブサポートはブラウザにも搭載される予定です。Safariにはすでに搭載されており、他のブラウザも現在搭載に向けて取り組んでいます。

事前準備

コードを書くまえに、簡単に開発環境を整えておきましょう。

CodePen』を立ち上げ、必要に応じてカスタムセットアップ/リポジトリへの移行をおすすめします。

必要なのはHTML/JavaScriptファイルだけで、ライブラリのインポートにはSkypackを使用します。特別なビルドプロセスは必要ありません。

機能1. 色の組み合わせを「作る」

まずは、色彩理論に基づいた色相環からはじめましょう。

Coloring with Code

▲出典:codrops

上の色相環は、LCHの色相を12色で表現しています。

ベースカラーを選んだあと、特定の角度にある色を選ぶという流れを繰り返して、さまざまな組み合わせを作ってみましょう。

補色パレットを作る場合は、対極の位置にある色を選びます。

Coloring with Code

▲出典:codrops

3色を組み合わせた「トライアド」のパレットを作る場合は、ベースカラーから120度の位置にある色を2色選びます。

Coloring with Code

▲出典:codrops

組み合わせを変えれば、さまざまなカラーパレットを作ることができます。

Coloring with Code

▲出典:codrops

5種類のカラーパレットができました。

ここからは、いよいよコードを見ていきましょう。まずコードを提示してから、内容を詳しく説明します。

コード紹介

コード

function adjustHue(val) {
if (val < 0) val += Math.ceil(-val / 360) * 360;

return val % 360;
}

function createScientificPalettes(baseColor) {
const targetHueSteps = {
analogous: [0, 30, 60],
triadic: [0, 120, 240],
tetradic: [0, 90, 180, 270],
complementary: [0, 180],
splitComplementary: [0, 150, 210]
};

const palettes = {};

for (const type of Object.keys(targetHueSteps)) {
palettes[type] = targetHueSteps[type].map((step) => ({
l: baseColor.l,
c: baseColor.c,
h: adjustHue(baseColor.h + step),
mode: "lch"
}));
}

return palettes;
}

解説

  • createScientificPalettesを定義し、baseColorを引数として受け取る
  • クラシックなカラーパレットの色相ステップを定義する
  • 各パレットのタイプについて、色相ステップを繰り返し、ステップの値をベース色相に追加して、結果の色を保存
  • chromalightness の値がベース色と一致することを確認
  • adjustHue関数を使って、すべての色相値が0から360の間にあることを確認
  • パレットをLCHフォーマットで返す

活用方法

createScientificPalettes関数を以下のように呼び出しましょう。

const baseColor = {
l: 50,
c: 100,
h: 0,
mode: "lch"
};

const palettes = createScientificPalettes(baseColor);

上の例では、baseColor オブジェクトを渡すと、それを中心としたさまざまなパレットが返されます。

LCHカラーを使っているため、パレットの明度と色彩強度が視覚的に一貫しています。他の色空間と違い、色の知覚コントラストが同じなので、アクセシビリティも高いのが特徴です。

あとは、LCHカラーをより使いやすい形式に変換するだけ。チュートリアルで使われているカラーユーティリティライブラリ『Culori』を使えば、LCHオブジェクトをHEXなどに変換できます。

import { formatHex } from "https://cdn.skypack.dev/culori@2.0.0";

const baseColor = {
l: 50,
c: 100,
h: 0,
mode: "lch"
};

const palettes = createScientificPalettes(baseColor);
const triadicHex = palettes.triadic.map((colorLCH) => formatHex(colorLCH));

// ["#ff007c", "#1f8a00", "#0091ff"]

Culoriは、すべてのカラーオブジェクトに明示modeを要求します。このチュートリアルのコード例からもわかるはずです。

最初の関数は、これで完了です。では、実際の使用例を見てみましょう。

実用例

カラーパレットをコード(プログラム)で作ることのメリットは、ラピッドプロトタイピングや実験が非常に簡単になることです。

たとえばデザインに取り組んでいて、どのカラーパレットを使うべきか完全に行き詰まってしまったとします。createScientificPalettes関数と簡単なCSSカスタムプロパティを使えば、ほぼ無限にパレットを作り、リアルタイムでUIをテストできるのです。

CodePenによるデモを見てみましょう。

See the Pen
Coloring With Code — Rapid Color Prototyping
by George Francis (@georgedoescode)
on CodePen.0

チャレンジ

現在、createScientificPalettes関数は、単色を除くすべてのパレットタイプに対応しています。さらに挑戦してみたい場合は、単色パレットをサポートするようにアップデートしてみましょう。

機能2. 色の組み合わせを「発見する」

クラシックな色の組み合わせを作るところまでは前回と同様ですが、今回は科学的に計算して色を選ぶのではなく、「発見」してみましょう。

色の配列を受け取り、その中から類似色、3色を組み合わせるトライアド、4色を組み合わせるテトラッドなど、最適なパレットを見つけます。以下はその図解です。

Coloring with Code

▲出典:codrops

この機能を使えば、画像やカラーデータセットなどの中から美しいパレットを発見できます。では、その仕組みを掘り下げてみましょう。

コード紹介

コード

import {
nearest,
differenceEuclidean,
} from "https://cdn.skypack.dev/culori@2.0.0";

function isColorEqual(c1, c2) {
return c1.h === c2.h && c1.l === c2.l && c1.c === c2.c;
}

function discoverPalettes(colors) {
const palettes = {};

for (const color of colors) {
const targetPalettes = createScientificPalettes(color);

for (const paletteType of Object.keys(targetPalettes)) {
const palette = [];
let variance = 0;

for (const targetColor of targetPalettes[paletteType]) {
// filter out colors already in the palette
const availableColors = colors.filter(
(color1) => !palette.some((color2) => isColorEqual(color1, color2))
);

const match = nearest(
availableColors,
differenceEuclidean("lch")
)(targetColor)[0];

variance += differenceEuclidean("lch")(targetColor, match);

palette.push(match);
}

if (!palettes[paletteType] || variance < palettes[paletteType].variance) {
palettes[paletteType] = {
colors: palette,
variance
};
}
}
}

return palettes;
}

解説

  • LCHカラーの配列をdiscoverPalettes関数に渡す
  • 各色について、createScientificPalettes関数でその色に基づいた最適なターゲットパレットを作成する
  • 各パレットについて、その色にもっとも近いものを探す。ここでは、Culoriのnearestk関数differenceEuclidiank関数を使い、色の一致を計算する
  • 発見されたパレットがターゲットとどの程度似ている/異なっているかを判断する。もっとも近いパレットのマッチを記録する
  • 各パレットタイプのもっとも近いマッチを返す

この方法は、人間が色の選択肢を見て、最適なパレットを見つけるのと同じように動きます。数学的な色彩理論を当てはめるのではなく、予測不可能な部分を残した刺激的な方法です。

活用方法

参考までに、HEXカラーの配列でdiscoverPalettesを使う方法をご紹介します。

import {
converter,
} from "https://cdn.skypack.dev/culori@2.0.0";

const toLCH = converter("lch");

const baseColors = [
"#FFB97A",
"#FF957C",
"#FF727F",
"#FF5083",
"#F02F87",
"#C70084",
"#9A007F",
"#6A0076",
"#33006B"
];

const baseColorsLCH = baseColors.map((color) => toLCH(color));

const palettes = discoverPalettes(baseColorsLCH);

// { analogous: [...], complementary: [...], ... }

discoverPalettesが正しく機能するためには、最低4色が必要です。

実用例

discoverPalettesのもっとも魅力的な点のひとつは、あらゆるソースから首尾一貫した色の組み合わせを引き出すことができる点です。以下の例では、Unsplashの画像をもとにパレットを発見しています。

See the Pen
Coloring With Code — Image Palette Extraction
by George Francis (@georgedoescode)
on CodePen.0

discoverPalettesを使って写真からパレットを抽出するという方法は、アイデアに行き詰まってしまったとき、役立ちます。このようなアプローチは、以前は魔法のようなカラージェネレーターやアプリをとおしてのみ可能でしたが、いまでは自分の好みにあわせて調整/改良可能です。

チャレンジ

現在、discoverPalettesには「色の配列から、もっともマッチするものを見つける」という機能があります。さらにチャレンジしたい場合は、「明るい色を優先する」といったヒエラルキーをつけてみましょう。

機能3. 色相を変えて色を組み合わせる

最後にご紹介するのは、ピクセルアートにヒントを得た方法です。

スプライトにシェードやハイライトを追加するとき、ピクセルアーティストは色の明度や彩度を調整するだけでなく、色相も変化させることがあります。以下がその例をあらわした図です。

Coloring with Code

▲出典:codrops

色が明るくなると色相が上がり、色が暗くなると色相が下がります。このテクニックをさりげなく使うことで、色の濃淡が鮮やかになり、インパクトのある仕上がりを演出できます。すこし強めに色相を調整すれば、スタンドアローンのカラーパレットを魅力的に演出できるはずです。

コード紹介

コード

function adjustHue(val) {
if (val < 0) val += Math.ceil(-val / 360) * 360;

return val % 360;
}

function map(n, start1, end1, start2, end2) {
return ((n - start1) / (end1 - start1)) * (end2 - start2) + start2;
}

function createHueShiftPalette(opts) {
const { base, minLightness, maxLightness, hueStep } = opts;

const palette = [base];

for (let i = 1; i < 5; i++) {
const hueDark = adjustHue(base.h - hueStep * i);
const hueLight = adjustHue(base.h + hueStep * i);
const lightnessDark = map(i, 0, 4, base.l, minLightness);
const lightnessLight = map(i, 0, 4, base.l, maxLightness);
const chroma = base.c;

palette.push({
l: lightnessDark,
c: chroma,
h: hueDark,
mode: "lch"
});

palette.unshift({
l: lightnessLight,
c: chroma,
h: hueLight,
mode: "lch"
});
}

return palette;
}

解説

  • createHueShiftPalette関数に、ベースカラー、最小/最大明度、色相ステップのパラメータを渡す。明度の最小/最大値は、パレットがどの程度暗く/明るくなるかを決定する。ステップの値は、各色で色相がどの程度シフトするかを制御する
  • ベースカラーを配列にストアする。上の図においては、中心にある色がこれにあたる
  • 4回反復するループを作る。反復のたびに配列の先頭に濃い色合いを、末尾に薄い色合いを追加。ここでは、mapを使って明度の値を計算し、hueStep変数を使って色相を増減させる。ここでもadjustHueが使われ、すべての色相が0から360の間にあることを確認する
  • パレットを返す

活用方法

createHueShiftPalette関数が定義されたら、以下のように使いましょう。

import { formatHex } from "https://cdn.skypack.dev/culori@2.0.0";

const hueShiftPalette = createHueShiftPalette({
base: {
l: 55,
c: 75,
h: 0,
mode: "lch"
},
minLightness: 10,
maxLightness: 90,
hueStep: 12
});

const hueShiftPaletteHex = hueShiftPalette.map((color) => formatHex(color));

// ["#ffb97a", "#ff957c", "#ff727f", "#ff5083", "#f02f87", "#c70084", "#9a007f", "#6a0076", "#33006b"]

実用例

createHueShiftPaletteで生成したパレットは、パターンやグラフィックにぴったりです。レンダリングするたびにすこしずつ変化する、ランダム/ジェネレーティブパターンの作成に使用した例を見てみましょう。

See the Pen
Coloring With Code — Generative Hue Shift Patterns
by George Francis (@georgedoescode)
on CodePen.0

このアプローチを活用すれば、つねに新鮮でユニークなUI要素を提供できます。

チャレンジ

現在、createHueShiftPalett関数では、明度/色相の値がリニアにスケールしています。さらにチャレンジしたい場合、イージングを適用して、色相のシフトを大きく/小さくし、各ステップでそれを増減させてみましょう。

おわりに

今回は、カラーパレットを作るための関数を3種類ご紹介しました。関数そのものだけでなく、適用方法や改善点についても触れたので、ぜひプロジェクトのニーズにあわせて調整してみてください。

(執筆:George Francis 翻訳:Asuka Nakajima 編集:mozuku 提供元:codrops

SHARE

  • 広告主募集
  • ライター・編集者募集
  • WorkshipSPACE
エンジニア副業案件
Workship