Reactアプリの続きです。前回はアスペクト比ジェネレーターを作りました。 今回もデザイン系のアプリで配色ジェネレーターを作りました!HSBモードでメインカラー、アクセントカラー、ベースカラーの3色を割り出せる配色ジェネレーターです。そのソースコードをここにまとめます(かなりのボリュームになりました…)。それではいきましょう!
【目次】
- 作ったもの
- このアプリで実現したかったこと
- ソースコード
- データ:data.json
- モジュール:colorConversionフォルダ
- Hex→RGB変換:hexToRgbモジュール
- RGB→HSB変換:rgbToHsbモジュール
- HSB→RGB変換:hsbToRgbモジュール
- RBG→Hex変換:rgbToHexモジュール
- HSB→Hex変換:hsbToHexモジュール
- アクセントカラー色相:accentColorHueモジュール
- ベースカラー、メインカラーの算出
- ベースカラー彩度:baseColorSaturationモジュール
- ベースカラー明度:baseColorBrightnessモジュール
- メインカラー彩度:mainColorSaturationモジュール
- メインカラー明度:mainColorBrightnessモジュール
- ベースカラーのコントラスト:changeConstractモジュール
- Innerコンポーネント
- 最後に
※参考:前回記事
【React】アスペクト比ジェネレーターを作った(画像の縦横比率を計算するツール) - クモのようにコツコツと
※参考:ReactでWebアプリを作るシリーズまとめ
qiita.com
作ったもの
配色ジェネレーター
- カラーバーの面積比はメインカラー(25%)、アクセントカラー (5%)、ベースカラー(70%)
- カラーピッカーから色を変更すると他の2色も相関して変更される
- メインカラーは色相(H)、彩度(S)、明度(B)で変更できる
- アクセントカラーはメインカラーに対する補色で、表色系により位置が変わる
- ベースカラーはメインカラーをコントラスト0-99%は薄く、101-200%は濃くした色
作ったアプリはこちら
※参考:配色ジェネレーター
アプリの使い方
ソースコード
※参考:GitHub - ryo-i/color-scheme-generator
このアプリで実現したかったこと
下記の記事で書いたようなメインカラー、アクセントカラー、ベースカラーの3色を割り出す配色ジェネレーターを作りたかった。
※参考:【配色の基本】面積比(メイン、アクセント、ベース)と色相分割【Adobe Color CC】 - クモのようにコツコツと
そして、配色設定は下記の記事のように、色相(H)、彩度(S)、明度(B)のHSBモードをベースに行う。
※参考:【配色】色相環のH値をいろいろ測ってみた(HSB、マンセル、オストワルト、PCCS、イッテン、NCS、Web配色ツール) - クモのようにコツコツと
アクセントカラー(補色)の色相(H)はいろいろな表色系によって位置がことなるので、その違いが調べられるようにしたかった。さらにキーカラーとキーカラーの間の補色も自動計算で算出したかった。
ソースコード
上記の挙動を実現しているソースコードはこちら。
※参考:GitHub - ryo-i/color-scheme-generator
(基本的なファイル構成は以前作った「Next.jsスターターキット」がベースになっている)
※参考:GitHub - ryo-i/next-app-started
以下、このアプリ固有な内容を書いてく。
データ:data.json
「data.json」の中にアプリにつかうデータの初期値を書いている。
※参考:color-scheme-generator/data.json at main · ryo-i/color-scheme-generator · GitHub
"inner": { "colorPicker": { "mainHex": "#e61717", "accentHex": "#1ee617", "baseHex": "#fce6e6" }, "mainColor": { "hue": 0, "saturation": 90, "brightness": 90 }, "accentColor": { "hue": 118, "saturation": 90, "brightness": 90, "hueCircle": "イッテン表色系", "hueCircleKey": "itten" }, "baseColor": { "hue": 0, "saturation": 9, "brightness": 99, "contrast": -90 }, "hueCircle": { "hsb": [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330], "munsell": [23, 44, 75, 165, 180, 198, 211, 300, 332, 352], "ostwald": [25, 48, 121, 184, 198, 207, 305, 344], "pccs": [19, 37, 53, 66, 153, 181, 201, 209, 251, 300, 330, 344], "itten": [3, 11, 22, 41, 59, 85, 122, 186, 214, 228, 265, 336], "ncs": [0, 35, 55, 70, 129, 153, 201, 293] }
colorPicker
はカラーピッカーの初期値mainColor
はメインカラーの初期値accentColor
はアクセントカラーの初期値baseColor
はベースカラーの初期値hueCircle
は各表色系の色相環のキーカラーの色相(H)
色相環のキーカラーについてはこちらを参照
※参考:【配色】色相環のH値をいろいろ測ってみた(HSB、マンセル、オストワルト、PCCS、イッテン、NCS、Web配色ツール) - クモのようにコツコツと
モジュール:colorConversionフォルダ
何度も実行するような細かい処理は下記のcolorConversionフォルダにモジュールとして切り出した。
※参考:color-scheme-generator/modules/colorConversion at main · ryo-i/color-scheme-generator · GitHub
Hex→RGB変換:hexToRgbモジュール
Hex値(ハッシュ#
以下の16進数6桁の値)をRGB(緑、赤、青の3原色)に変換するモジュール
※参考:color-scheme-generator/hexToRgb.ts at main · ryo-i/color-scheme-generator · GitHub
const hexToRgb = (hex: string) => { const hexColors: {r: string, b: string, g: string} = { r: hex.substring(1, 3), g: hex.substring(3, 5), b: hex.substring(5) }; const rgbColors: {r: number, b: number, g: number} = { r: parseInt(hexColors.r, 16), g: parseInt(hexColors.g, 16), b: parseInt(hexColors.b, 16) }; return rgbColors; } export { hexToRgb };
RGB→HSB変換:rgbToHsbモジュール
RGBの値をHSB(色相(H)、彩度(S)、明度(B))に変換するモジュール
※参考:color-scheme-generator/rgbToHsb.ts at main · ryo-i/color-scheme-generator · GitHub
const setHue = (r: number, g: number, b: number, max: number, min: number) => { let hue: number = 0; const difference: number = max - min; if (difference === 0) { hue = 0; } else if (max === r) { const result = 60 * ((g - b) / (difference)); hue = Math.round(result); } else if (max === g) { const result = 60 * ((b - r) / (difference)) + 120; hue = Math.round(result); } else if (max === b) { const result = 60 * ((r - g) / (difference)) + 240; hue = Math.round(result); } if (Math.sign(hue) === -1) { hue = hue + 360; } return hue; }; const setSaturation = (max: number, min: number) => { let saturation: number = 0; const difference: number = max - min; if (difference === 0) { saturation = 0; } else { saturation = Math.round((max - min) / max * 100); } return saturation; }; const setBrightness = (max: number, min: number) => { const brightness: number = Math.round(max / 255 * 100); return brightness; }; const rgbToHsb = (r: number, g: number, b: number) => { const max: number = Math.max(r, g, b); const min: number = Math.min(r, g, b); const hue: number = setHue(r, g, b, max, min); const saturation: number = setSaturation(max, min); const brightness: number = setBrightness(max, min); const hsb: {h: number, s: number, b: number} = { h: hue, s: saturation, b: brightness }; return hsb; }; export { rgbToHsb };
作成時のイシュー
※参考:HSBとRGBの変換方法 · Issue #3 · ryo-i/color-scheme-generator · GitHub
参考になった記事
※参考:JavaScriptでRGBをHEXに変換する方法
※参考:JavaScriptでHEXをRGBに変換する方法
※参考:【JavaScript】10進数と16進数の相互変換と16進表記の方法 | MaryCore
※参考:Number.parseInt() - JavaScript | MDN
※参考:Number.prototype.toString() - JavaScript | MDN
※参考:【Javascript】0で割ると? at softelメモ
※参考:ゼロ除算(0除算)とは - IT用語辞典 e-Words
RGBとHSBの関係性は今回初めて理解できた。自力ではこの計算式を算出するのは無理で先人のいろいろな記事を参考にさせていただいた。また、最大値と最小値の差分がゼロの時の割り算がエラーになり、「ゼロ除算」は数学のタブーと知った!
HSB→RGB変換:hsbToRgbモジュール
先ほどと逆でHSBをRGBに変換するモジュール
※参考:color-scheme-generator/hsbToRgb.ts at main · ryo-i/color-scheme-generator · GitHub
const hsbToRgb = (h: number, s: number, b: number) => { let red: number = 0; let green: number = 0; let blue: number = 0; const max: number = (b / 100) * 255; const min: number = Math.round(max - ((s / 100) * max)); const diff: number = max - min; if (h >= 0 && h <= 60) { red = max; green = (h / 60) * (diff) + min; blue = min; } else if (h >= 60 && h <= 120) { red = ((120 - h) / 60) * (diff) + min; green = max; blue = min; } else if (h >= 120 && h <= 180) { red = min; green = max; blue = ((h - 120) / 60) * (diff) + min; } else if (h >= 180 && h <= 240) { red = min; green = ((240 - h) / 60) * (diff) + min; blue = max; } else if (h >= 240 && h <= 300) { red = ((h - 240) / 60) * (diff) + min; green = min; blue = max; } else if (h >= 300 && h <= 360) { red = max; green = min; blue = ((360 - h) / 60) * (diff) + min; } const rgb: {r: number, g: number, b: number} = { r: Math.round(red), g: Math.round(green), b: Math.round(blue) }; return rgb; } export { hsbToRgb };
作成時のイシュー
※参考:HSBとRGBの変換方法 · Issue #3 · ryo-i/color-scheme-generator · GitHub
参考になった記事
※参考:RGBとHSV・HSBの相互変換ツールと変換計算式 - PEKO STEP
※参考:RGB←→HSB相互変換【Windowsプログラミング研究所】
※参考:Webツール|色の変換(RGB・HSV・HSL・16進数) | Hirota Yano
RBG→Hex変換:rgbToHexモジュール
RGBをHexに変換するモジュール
※参考:color-scheme-generator/rgbToHex.ts at main · ryo-i/color-scheme-generator · GitHub
const rgbToHex = (r: number, g: number, b: number) => { const rgbHex: {r: string, g: string, b: string} = { r: String(('00' + r.toString(16)).slice(-2)), g: String(('00' + g.toString(16)).slice(-2)), b: String(('00' + b.toString(16)).slice(-2)) }; const hex: string = '#' + rgbHex.r + rgbHex.g + rgbHex.b; return hex; } export { rgbToHex };
作成時のイシュー
※参考:HSBとRGBの変換方法 · Issue #3 · ryo-i/color-scheme-generator · GitHub
参考になった記事
※参考:https://wa3.i-3-i.info/word14905.html
※参考:JavaScriptで数値の桁数を合わせる(ゼロパディング)方法 - JavaScriptテックラボ - [SMART]
※参考:Array.prototype.slice() - JavaScript | MDN
数字の桁を合わせる「ゼロパディング」便利!
HSB→Hex変換:hsbToHexモジュール
HSBをHexに変換するモジュール
※参考:color-scheme-generator/hsbToHex.ts at main · ryo-i/color-scheme-generator · GitHub
import { hsbToRgb } from './hsbToRgb'; import { rgbToHex } from './rgbToHex'; const hsbToHex = (hue: number, saturation: number, brightness: number,) => { const getRgb = hsbToRgb(hue, saturation, brightness); const getHex = rgbToHex(getRgb.r, getRgb.g, getRgb.b); return getHex; } export { hsbToHex };
hsbToRgb
モジュールとrgbToHex
モジュールを読み込んで同時に実行した結果を返す処理。
作成時のイシュー
※参考:hsbToHex()モジュールを作る · Issue #17 · ryo-i/color-scheme-generator · GitHub
アクセントカラー色相:accentColorHueモジュール
メインカラーの色相(H)からアクセントカラーの色相(H)を算出するモジュール
※参考:color-scheme-generator/accentColorHue.ts at main · ryo-i/color-scheme-generator · GitHub
import { inner } from '../../data/data.json'; const accentColorHue = (mainColorHue: number, hueCircleKey: string) => { const keyColor: number[] = inner.hueCircle[hueCircleKey]; const KeyLength: number = keyColor.length; const keyHalfLength: number = KeyLength /2; let mainColorNum: number = 0; let mainColorKey: number = 0; let mainColorDiff: number = 0; let nextMeinColorKye: number = 0; let nextMainColorDiff: number = 0; const setNextMainColor = (keyColor: number, mainColorKey: number) => { nextMeinColorKye = keyColor; nextMainColorDiff = nextMeinColorKye - mainColorKey; if (nextMainColorDiff < 0) { nextMainColorDiff = nextMainColorDiff + 360; } }; for (let i = 0; i < keyColor.length; i++) { if (keyColor[i] > mainColorHue && keyColor[i] !== keyColor[0]) { mainColorNum = i - 1; mainColorKey = keyColor[mainColorNum]; mainColorDiff = mainColorHue - mainColorKey; setNextMainColor(keyColor[i], mainColorKey); break; } else if (keyColor[i] > mainColorHue && keyColor[i] === keyColor[0]) { mainColorNum = keyColor.length - 1; mainColorKey = keyColor[mainColorNum]; mainColorDiff = (360 - mainColorKey) + mainColorHue; setNextMainColor(keyColor[i], mainColorKey); break; } else if (keyColor[i] <= mainColorHue && keyColor[i] === keyColor[keyColor.length - 1]) { mainColorNum = i; mainColorKey = keyColor[mainColorNum]; mainColorDiff = mainColorHue - mainColorKey; setNextMainColor(keyColor[0], mainColorKey); break; } } let accentColorNum: number = mainColorNum + keyHalfLength; let accentColorKey: number = keyColor[accentColorNum]; if (!accentColorKey) { accentColorNum = accentColorNum - KeyLength; accentColorKey = keyColor[accentColorNum]; } let nextAccentColorKye: number = keyColor[accentColorNum + 1]; if (!nextAccentColorKye) { nextAccentColorKye = keyColor[0]; } let nextAccentColorDiff: number = nextAccentColorKye - accentColorKey; if (nextAccentColorDiff < 0) { nextAccentColorDiff = nextAccentColorDiff + 360; } const accentColorUnit: number = nextAccentColorDiff / nextMainColorDiff; const accentColorDiff: number = Math.round(mainColorDiff * accentColorUnit); let accentColorHue: number = accentColorKey + accentColorDiff; if (accentColorHue > 360) { accentColorHue = accentColorHue - 360; } return accentColorHue; } export { accentColorHue };
作成時のイシュー
※参考:H値の色相環上のズレ問題 · Issue #2 · ryo-i/color-scheme-generator · GitHub
※参考:アクセントカラーを動的に変更する · Issue #10 · ryo-i/color-scheme-generator · GitHub
ベースカラー、メインカラーの算出
ここから先のベースカラーとメインカラーの値の算出は数学の基礎力がないため自分の独力では実現が困難だった。理系出身の弟に相談しかなり協力を得てようやく実現できた。
作成時のイシュー
※参考:ベースカラーの計算方法 · Issue #6 · ryo-i/color-scheme-generator · GitHub
※参考:ベースカラーを動的に変更する · Issue #11 · ryo-i/color-scheme-generator · GitHub
※参考:ベースカラーからメインカラーを算出 · Issue #21 · ryo-i/color-scheme-generator · GitHub
※参考:ベースカラーの範囲変更(-100〜100%に) · Issue #22 · ryo-i/color-scheme-generator · GitHub
参考になった記事
※参考:逆算のやり方総まとめ!中学受験やSPI対策にも | そうちゃ式 受験算数(新1号館 数論/特殊算)
※参考:問50の様に□が2つある場合の四則逆算の解き方を教えてください。 - Yahoo!知恵袋
※参考:方程式の解き方をマスターしよう|中学生/数学 |【公式】家庭教師のアルファ-プロ講師による高品質指導
自分は「等式の性質」など基本的な逆算や方程式の方法をほとんど忘れていた。。
ベースカラー彩度:baseColorSaturationモジュール
メインカラーの彩度(S)からベースカラーの彩度(S)を算出するモジュール
※参考:color-scheme-generator/baseColorSaturation.ts at main · ryo-i/color-scheme-generator · GitHub
const baseColorSaturation = (contrast: number, saturation: number) => { let baseColorSaturation: number = 0; if (contrast == 0) { baseColorSaturation = saturation; } else if (contrast < 0) { baseColorSaturation = saturation * (1 + contrast / 100); } else if (contrast > 0) { baseColorSaturation = saturation + (100 - saturation) * contrast / 100; } return Math.round(baseColorSaturation); } export { baseColorSaturation };
ベースカラー明度:baseColorBrightnessモジュール
メインカラーの明度(B)からベースカラーの明度(B)を算出するモジュール
※参考:color-scheme-generator/baseColorBrightness.ts at main · ryo-i/color-scheme-generator · GitHub
const baseColorBrightness = (contrast: number, brightness: number) => { let baseColorBrightness: number = 0; if (contrast == 0) { baseColorBrightness = brightness; } else if (contrast < 0) { baseColorBrightness = brightness - (100 - brightness) * (contrast / 100) } else if (contrast > 0) { baseColorBrightness = brightness * (1 - (contrast / 100)); } return Math.round(baseColorBrightness); } export { baseColorBrightness };
メインカラー彩度:mainColorSaturationモジュール
ベースカラーの彩度(S)からメインカラーの彩度(S)を算出するモジュール
※参考:color-scheme-generator/mainColorSaturation.ts at main · ryo-i/color-scheme-generator · GitHub
const mainColorSaturation = (contrast: number, saturation: number) => { let resultSaturation: number = 0; const absContrast: number = Math.abs(contrast); if (contrast == 0) { resultSaturation = saturation; } else if (contrast < 0) { resultSaturation = saturation / (1 - absContrast / 100); } else if (contrast > 0) { resultSaturation = (saturation - absContrast) / (1 - absContrast / 100); } return Math.round(resultSaturation); } export { mainColorSaturation };
作成時のイシュー ※参考:ベースカラー→メインカラー変換で彩度Sと明度Bがオーバーフロー · Issue #23 · ryo-i/color-scheme-generator · GitHub
ベースカラー→メインカラーの算出ではコントラストの位置が保てないオーバーフロー問題が発生。「絶対値」を用いてプラスマイナスではなくゼロからの相対的な位置関係を元に計算する必要があった。
参考になった記事
※参考:絶対値とは何か?なんの意味があるのか?簡単に解説してみた
※参考:JavaScript | 絶対値を取得する(Math.abs)
メインカラー明度:mainColorBrightnessモジュール
ベースカラーの明度(B)からメインカラーの明度(B)を算出するモジュール
※参考:color-scheme-generator/mainColorBrightness.ts at main · ryo-i/color-scheme-generator · GitHub
const mainColorBrightness = (contrast: number, brightness: number) => { let resultBrightness: number = 0; const absContrast: number = Math.abs(contrast); if (contrast == 0) { resultBrightness = brightness; } else if (contrast < 0) { resultBrightness = (brightness - absContrast) / (1 - absContrast / 100); } else if (contrast > 0) { resultBrightness = brightness / (1 - absContrast / 100); } return Math.round(resultBrightness); } export { mainColorBrightness };
こちらも同様に絶対値を用いる。
ベースカラーのコントラスト:changeConstractモジュール
ベースカラーのコントラスト比を変更するモジュール
※参考:color-scheme-generator/changeConstract.ts at main · ryo-i/color-scheme-generator · GitHub
ベースカラー→メインカラーの算出でオーバーフローが起きたときにコントラスト比を変更してオーバーフローが治らない範囲に収めている。
const changeConstract = (contrast: number, saturation: number, brightness: number) => { const absContrast: number = Math.abs(contrast); const absSaturation: number = Math.abs(saturation); const absBrightness: number = Math.abs(brightness); let checkSaturationVal: number = 0; let checkBrightnessionVal: number = 0; let saturationFlag: boolean = false; let brightnessFlag: boolean = false; let saturationDiff: number = 0; let brightnessDiff: number = 0; let resultContrast: number = contrast; // Check Saturation Diff if (contrast < 0) { checkSaturationVal = absSaturation + absContrast; saturationFlag = (checkSaturationVal) <= 100 ? true : false; saturationDiff = saturationFlag ? 0 : checkSaturationVal - 100; } else if (contrast > 0) { checkSaturationVal = absSaturation - absContrast; saturationFlag = 0 <= (checkSaturationVal) ? true : false; saturationDiff = saturationFlag ? 0 : -(checkSaturationVal); } // Check Brightnession Diff if (contrast < 0) { checkBrightnessionVal = absBrightness - absContrast; brightnessFlag = 0 <= (checkBrightnessionVal) ? true : false; brightnessDiff = brightnessFlag ? 0 : -(checkBrightnessionVal); } else if (contrast > 0) { checkBrightnessionVal = absBrightness + absContrast; brightnessFlag = (checkBrightnessionVal) <= 100 ? true : false; brightnessDiff = brightnessFlag ? 0 : checkBrightnessionVal -100; } // Change Flag (Saturation or Brightness) if (saturationDiff > brightnessDiff && !brightnessFlag) { brightnessFlag = true; } else if (saturationDiff < brightnessDiff && !saturationFlag) { saturationFlag = true; } // Result Contrast if (saturationFlag && brightnessFlag) { resultContrast = absContrast; } else if (!saturationFlag && contrast < 0) { resultContrast = 100 - saturation; } else if (!saturationFlag && contrast > 0) { resultContrast = saturation; } else if (!brightnessFlag && contrast < 0) { resultContrast = brightness; } else if (!brightnessFlag && contrast > 0) { resultContrast = 100 - brightness; } // Change Contrast (Plus or Minus) if (contrast < 0 && resultContrast > 0) { resultContrast = -(resultContrast); } return resultContrast; } export { changeConstract };
Innerコンポーネント
InnerコンポーネントでReactコンポーネントを実行している。上記のデータやモジュールを読み込んで実行している。
※参考:color-scheme-generator/Inner.tsx at main · ryo-i/color-scheme-generator · GitHub
このアプリの本丸なので作成時のイシューはたくさん…
※参考:History for components/Inner.tsx - ryo-i/color-scheme-generator · GitHub
モジュール類のimport
Reactフック(useState、useEffect)、CSS in JS(styled-components)や上記のデータ、モジュール類をインポート
import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { inner } from '../data/data.json'; import { hexToRgb } from '../modules/colorConversion/hexToRgb'; import { rgbToHsb } from '../modules/colorConversion/rgbToHsb'; import { hsbToRgb } from '../modules/colorConversion/hsbToRgb'; import { rgbToHex } from '../modules/colorConversion/rgbToHex'; import { hsbToHex } from '../modules/colorConversion/hsbToHex'; import { accentColorHue } from '../modules/colorConversion/accentColorHue'; import { baseColorSaturation } from '../modules/colorConversion/baseColorSaturation'; import { baseColorBrightness } from '../modules/colorConversion/baseColorBrightness'; import { mainColorSaturation } from '../modules/colorConversion/mainColorSaturation'; import { mainColorBrightness } from '../modules/colorConversion/mainColorBrightness'; import { changeConstract } from '../modules/colorConversion/changeConstract';
CSS in JS設定
styled-componentsで後述するJSXタグのCSSを設定している。
JSXResult
タグに適用されるスタイル
// Style const Result = styled.div` margin: 0 0 30px; .colorPalette { display: flex; width: 100%; margin 0 0 10px; border: 1px #ddd solid; div { height: 80px; } .mainColor { width: 25%; } .accentColor { width: 5%; border-left: 1px #ddd solid; border-right: 1px #ddd solid; } .baseColor { width: 70%; } } .colorPicker { input, label { :hover { cursor: pointer; } } label { margin: 0 1em 0 0; display: inline-block; } input[type="color"] { margin: 0 0.25em 0 0; padding: 0; border: none; background: none; appearance: none; width: 2em; height: 1em; ::-webkit-color-swatch { border: #eee 1px solid; border-radius: 3px; } ::-webkit-color-swatch-wrapper { margin: 0; padding: 0; width: 2em; height: 1em; border: none; } } } `;
JSXGenerator
タグに適用されるスタイル
const Generator = styled.div` margin: 0 0 30px; h2 span { font-size: 14px; font-weight: normal; color: #000; } p { margin: 0; } .colorPalette { border: 1px solid #eee; } input, label { :hover { cursor: pointer; } } input[type='color'] { font-size: 16px; } input[type='range'] { width: 100%; } label { margin: 0 0.5em 0 0; display: inline-block; } `;
styled-componentsについてはこちらを参照
※参考:【React】styled-componentsでCSS in JSを事始める - クモのようにコツコツと
Innerコンポーネント全体
このコンポーネントファイルの主要な部分。下記のようなパートに分かれる。
// Component function Inner() { // Color Picker Hooks // 中略(カラーピッカーのフック) // Main Color Hooks // 中略(メインカラーのフック) // Accent Color Hooks // 中略(アクセントカラーのフック) // base Color Hooks // 中略(ベースカラーのフック) // Change Color Picker const changeColorPicker = (e: React.ChangeEvent<HTMLInputElement>) => { // 中略(カラーピッカー変更時の処理) } }; // Change Main Color const changeMainColor = (e: React.ChangeEvent<HTMLInputElement>) => { // 中略(メインカラー変更時の処理) }; // Change Accent Color const changeAccentColor = (e: React.ChangeEvent<HTMLInputElement>) => { // 中略(アクセントカラー変更時の処理) }; // Change Base Color const changeBaseColor = (e: React.ChangeEvent<HTMLInputElement>) => { // 中略(ベースカラー変更時の処理) }; // Change CSS // 中略(動的なCSS変更) // JSX return ( // 中略(JSXタグの設定) ); } export default Inner;
最後にInner
コンポーネント全体をエクスポートしている。
フック設定
フックとそこに入る初期値を設定
// Color Picker Hooks const [mainHex, setMainHex] = useState(inner.colorPicker.mainHex); const [accentHex, setAccentHex] = useState(inner.colorPicker.accentHex); const [baseHex, setBaseHex] = useState(inner.colorPicker.baseHex); // Main Color Hooks const [mainHue, setMainHue] = useState(inner.mainColor.hue); const [mainSaturation, setMainSaturation] = useState(inner.mainColor.saturation); const [mainBrightness, setMainBrightness] = useState(inner.mainColor.brightness); // Accent Color Hooks const [accentHue, setAccentHue] = useState(inner.accentColor.hue); const [accentSaturation, setAccentSaturation] = useState(inner.accentColor.saturation); const [accentBrightness, setAccentBrightness] = useState(inner.accentColor.brightness); const [hueCircle, setHueCircle] = useState(inner.accentColor.hueCircle); const [hueCircleKey, setHueCircleKey] = useState(inner.accentColor.hueCircleKey); // base Color Hooks const [baseHue, setBaseHue] = useState(inner.baseColor.hue); const [baseSaturation, setBaseSaturation] = useState(inner.baseColor.saturation); const [baseBrightness, setBaseBrightness] = useState(inner.baseColor.brightness); const [contrast, setContrast] = useState(inner.baseColor.contrast);
カラーピッカー、メインカラー、アクセントカラー、ベースカラーの順で設定。useState()
でinner
(data.json)から初期値を読み込んでいる。
フックについてはこちらを参照
※参考:【React】フック(React Hooks)事始め:useState、useEffect、useContext - クモのようにコツコツと
カラーピッカー変更時の処理:changeColorPicker()
ここからinputタグの変更時に行われる処理。基本的にはに各モジュールを実行して、その結果をフックに登録している。
カラーピッカーの値が変わったときに、そのカラーピッカーがメインカラー、アクセントカラー、ベースカラーのどれかによって違う処理をしている。それぞれ、他の2色も連動して変更する。ベースカラー変更時にメインカラーがオーバーフローする場合はベースカラーのコントレストも変更する。
// Change Color Picker const changeColorPicker = (e: React.ChangeEvent<HTMLInputElement>) => { const getName:string = String(e.target.name); const getValue: string = String(e.target.value); const rgb = hexToRgb(getValue); const hsb = rgbToHsb(rgb.r, rgb.g, rgb.b); if (getName === 'mainHex') { // Main Color setMainHue(hsb.h); setMainSaturation(hsb.s); setMainBrightness(hsb.b); setMainHex(getValue); // Accent Color const getAccentHue: number = accentColorHue(hsb.h, hueCircleKey); const getAccentHex: string = hsbToHex(getAccentHue, hsb.s, hsb.b); setAccentHue(getAccentHue); setAccentSaturation(hsb.s); setAccentBrightness(hsb.b); setAccentHex(getAccentHex); // Base Color const getBaseSaturation: number = baseColorSaturation(contrast, hsb.s); const getBaseBrightness: number = baseColorBrightness(contrast, hsb.b); const getBaseHex: string = hsbToHex(hsb.h, getBaseSaturation, getBaseBrightness); setBaseHue(hsb.h); setBaseSaturation(getBaseSaturation); setBaseBrightness(getBaseBrightness); setBaseHex(getBaseHex); } else if (getName === 'accentHex') { // Accent Color setAccentHue(hsb.h); setAccentSaturation(hsb.s); setAccentBrightness(hsb.b); setAccentHex(getValue); // Main Color const getMainHue: number = accentColorHue(hsb.h, hueCircleKey); const getMainHex: string = hsbToHex(getMainHue, hsb.s, hsb.b); setMainHue(getMainHue); setMainSaturation(hsb.s); setMainBrightness(hsb.b); setMainHex(getMainHex); // Base Color const getBaseSaturation: number = baseColorSaturation(contrast, hsb.s); const getBaseBrightness: number = baseColorBrightness(contrast, hsb.b); const getBaseHex: string = hsbToHex(getMainHue, getBaseSaturation, getBaseBrightness); setBaseHue(getMainHue); setBaseSaturation(getBaseSaturation); setBaseBrightness(getBaseBrightness); setBaseHex(getBaseHex); } else if (getName === 'baseHex') { // Base Color setBaseHue(hsb.h); setBaseSaturation(hsb.s); setBaseBrightness(hsb.b); setBaseHex(getValue); // Contrast const getConstract: number = changeConstract(contrast, hsb.s, hsb.b); setContrast(getConstract); // Main Color const getMainSaturation: number = mainColorSaturation(getConstract, hsb.s); const getMainBrightness: number = mainColorBrightness(getConstract, hsb.b); const getMainHex: string = hsbToHex(hsb.h, getMainSaturation, getMainBrightness); setMainHue(hsb.h); setMainSaturation(getMainSaturation); setMainBrightness(getMainBrightness); setMainHex(getMainHex); // Accent Color const getAccentHue: number = accentColorHue(hsb.h, hueCircleKey); const getAccentHex: string = hsbToHex(getAccentHue, getMainSaturation, getMainBrightness); setAccentHue(getAccentHue); setAccentSaturation(getMainSaturation); setAccentBrightness(getMainBrightness); setAccentHex(getAccentHex); } };
メインカラー変更時の処理
メインカラーのrangeバーを変えた時に、そのバーが色相(H)、彩度(S)、明度(B)かによって異なる処理をしている。アクセントカラー、ベースカラーも連動して変更する。
// Change Main Color const changeMainColor = (e: React.ChangeEvent<HTMLInputElement>) => { const getName: string = String(e.target.name); const getValue: number = Number(e.target.value); let mainRgb: {r: number, g: number, b: number}; let accentRgb: {r: number, g: number, b: number}; let baseRgb: {r: number, g: number, b: number}; if (getName === 'hue') { const getAccentHue: number = accentColorHue(getValue, hueCircleKey); setMainHue(getValue); setAccentHue(getAccentHue); setBaseHue(getValue); mainRgb = hsbToRgb(getValue, mainSaturation, mainBrightness); accentRgb = hsbToRgb(getAccentHue, accentSaturation, accentBrightness); baseRgb = hsbToRgb(getValue, baseSaturation, baseBrightness); } else if (getName === 'saturation') { const getBaseSaturation: number = baseColorSaturation(contrast, getValue); setMainSaturation(getValue); setAccentSaturation(getValue); setBaseSaturation(getBaseSaturation); mainRgb = hsbToRgb(mainHue, getValue, mainBrightness); accentRgb = hsbToRgb(accentHue, getValue, accentBrightness); baseRgb = hsbToRgb(baseHue, getBaseSaturation, baseBrightness); } else if (getName === 'brightness') { const getBaseBrightness: number = baseColorBrightness(contrast, getValue); setMainBrightness(getValue); setAccentBrightness(getValue); setBaseBrightness(getBaseBrightness); mainRgb = hsbToRgb(mainHue, mainSaturation, getValue); accentRgb = hsbToRgb(accentHue, accentSaturation, getValue); baseRgb = hsbToRgb(baseHue, baseSaturation, getBaseBrightness); } const getMainHex: string = rgbToHex(mainRgb.r, mainRgb.g, mainRgb.b); const getAccentHex: string = rgbToHex(accentRgb.r, accentRgb.g, accentRgb.b); const getBaseHex: string = rgbToHex(baseRgb.r, baseRgb.g, baseRgb.b); setMainHex(getMainHex); setAccentHex(getAccentHex); setBaseHex(getBaseHex); };
アクセントカラー変更時の処理
アクセントカラーのチェックボックスの表色系を変更した時にアクセントカラー の色相(H)を変更する処理を実行する。
// Change Accent Color const changeAccentColor = (e: React.ChangeEvent<HTMLInputElement>) => { const getValue: string = String(e.target.value); const hueCircleKey: string = e.target.dataset.hueCircle; const mainColorHue: number = mainHue; const getAccentHue = accentColorHue(mainColorHue, hueCircleKey); const getAccentHex = hsbToHex(getAccentHue, accentSaturation, accentBrightness); setAccentHue(getAccentHue); setAccentHex(getAccentHex); setHueCircle(getValue); setHueCircleKey(hueCircleKey); };
ベースカラー変更時の処理
ベースカラーのアクセント比を変更した時にベースカラーの彩度(S)、明度(B)を変更する。メインカラーの彩度(S)、明度(B)も変更する。
// Change Base Color const changeBaseColor = (e: React.ChangeEvent<HTMLInputElement>) => { const getValue: number = Number(e.target.value); const getBaseSaturation = baseColorSaturation(getValue, mainSaturation); const getBaseBrightness = baseColorBrightness(getValue, mainBrightness); const getBaseHex = hsbToHex(baseHue, getBaseSaturation, getBaseBrightness); setContrast(getValue); setBaseSaturation(getBaseSaturation); setBaseBrightness(getBaseBrightness); setBaseHex(getBaseHex); };
動的なCSS変更
上記の関数によってメインカラー、アクセントカラー 、ベースカラーが変更された時に、画面上のタグの色も動的に変更する。下記のJSXタグに設定する。
// Change CSS const mainColorPalette = { background: mainHex } const accentColorPalette = { background: accentHex } const baseColorPalette = { background: baseHex }
JSX設定
画面上に表示されるJSXタグ設定と、そのタグの属性やテキストの変更、関数を実行するイベントなどを設定している。
// JSX return ( <> <Result> <div className="colorPalette"> <div className="mainColor" style={mainColorPalette}></div> <div className="accentColor" style={accentColorPalette}></div> <div className="baseColor" style={baseColorPalette}></div> </div> <p className="colorPicker"> カラーピッカー: <label><input type="color" name="mainHex" value={mainHex} onChange={changeColorPicker} />{mainHex}</label> <label><input type="color" name="accentHex" value={accentHex} onChange={changeColorPicker} />{accentHex}</label> <label><input type="color" name="baseHex" value={baseHex} onChange={changeColorPicker} />{baseHex}</label> </p> </Result> <Generator> <section className="mainColor"> <h2>メインカラー<span>(H:{mainHue}, S:{mainSaturation}, B:{mainBrightness})</span></h2> <p>色相(H):{mainHue}</p> <input type="range" name="hue" value={mainHue} min="0" max="360" onChange={changeMainColor} /> <p>彩度(S):{mainSaturation}</p> <input type="range" name="saturation" value={mainSaturation} min="0" max="100" onChange={changeMainColor} /> <p>明度(B):{mainBrightness} </p> <input type="range" name="brightness" value={mainBrightness} min="0" max="100" onChange={changeMainColor} /> </section> <section className="accentColor"> <h2>アクセントカラー<span>(H:{accentHue}, S:{accentSaturation}, B:{accentBrightness})</span></h2> <p>色相環:{hueCircle}</p> <label><input type="radio" name="hueCircle" value="HSB表色系" data-hue-circle="hsb" onChange={changeAccentColor} />HSB</label> <label><input type="radio" name="hueCircle" value="マンセル表色系" data-hue-circle="munsell" onChange={changeAccentColor} />マンセル</label> <label><input type="radio" name="hueCircle" value="オストワルト表色系" data-hue-circle="ostwald" onChange={changeAccentColor} />オストワルト</label> <label><input type="radio" name="hueCircle" value="PCCS表色系" data-hue-circle="pccs" onChange={changeAccentColor} />PCCS</label> <label><input type="radio" name="hueCircle" value="イッテン表色系" data-hue-circle="itten" onChange={changeAccentColor} defaultChecked />イッテン</label> <label><input type="radio" name="hueCircle" value="NCS表色系" data-hue-circle="ncs" onChange={changeAccentColor} />NCS</label> </section> <section className="baseColor"> <h2>ベースカラー<span>(H:{baseHue}, S:{baseSaturation}, B:{baseBrightness})</span></h2> <p>コントラスト:{contrast}%</p> <input type="range" name="contrast" value={contrast} min="-100" max="100" onChange={changeBaseColor} /> </section> </Generator> </> );
参考になった記事
※参考:data属性の名前に使える文字列 | cly7796.net
※参考:HTMLElement.dataset - Web API | MDN
data-setの値に使える表記のルールに詳しくなった。
最後に
とういことで駆け足で書きましたがそれでもかなりのボリュームになりました。今回はこれまで作ったものよりもかなり混み入った計算をいくつも実行しており、これを手作業で計算しようとすると膨大な作業になるはずです!
また、やりたいことを実現するにあたり、等式の性質、絶対値、ゼロ除算、など数学の基礎的な能力が自分は欠如していることがわかり、とても勉強になりました。今後のために数学の基礎力も上げていきたいと思います。
それではまた!
※参考:ReactでWebアプリを作るシリーズまとめ
qiita.com