【React】快適に使えるカラー変換ツールを作った
はじめに
先日、ブラウザで使えるカラー変換ツールを探していました。カラーコード⇄RGBを変換したり、HSL⇄HSVを変換したりするツールのことをカラー変換ツールと呼んでいます。
しかし、個人的に欲しいと思っていた以下の4つの条件を満たすものを、Google検索の上位では見つけることができませんでした(僕が知らないだけかもしれませんが…)。
- 一覧性が高い。
- すべてのカラーモードで数値の入力・コピーに対応している。
- マウスクリック等の操作をできるだけ必要としない。
- 塗り・文字色の両方で色を確認できる。
そこで、ちょうど簡単に作れるWebツールを作ってみたいと思っていたところだったので、この機会に作ってみることにしました。
↓こんな感じのシンプルなサイトを作りました。
↓アクセスはこちらから
この記事では、快適に使えるカラー変換ツールを目指して工夫したポイントと、その実装について紹介していければと思います。
技術構成
- Next.js (TypeScript)
- React Context (状態管理)
- Emotion (CSS)
- Cloudflare Pages
- Google アナリティクス
開発には7日かかりました。Google アナリティクスをはじめて使ったので、その設定とプライバシーポリシーに手間取りました。React Contextもはじめて使いました。
こだわりポイントと実装
開発を始める前に
まず、よく使うカラーモードとして、Hex(カラーコード), RGBA, HSLA, HSV, CMYKの5つのカラーモードに対応させようと決めました。
次に、カラー変換ツールとして必須だと思ったのは、すべてのカラーモードの値がレスポンシブに更新されることです。例えばカラーコードを入力した際、Enterキーを押したりボタンをクリックしたりすることなくRGBやHSL等の数値が更新されることです。
それから、今回作るツールは「キーボードでの操作を前提とする」という方針で作ることに決めました。
カラーの状態管理
すべてのカラーモードの値を連動させるにあたって、5つのカラーモードの値とは別に、それらが共有するカラーの変数を作成しました。どれかひとつのカラーモードで値が変更されると、この共有された変数が更新され、他のカラーモードの値にも反映される、という具合です。どのカラーモードとも変換可能である必要があるため、一番扱いやすいRGBAを使って管理することにしました。このRGBA値が、実際に画面に表示されるサンプルの色を指定しています。
今回は、ReactのContextを使って変数を作り、sharedRgba
という名前で使うことにしました。Context用のファイルを作り、RGBA値に関連するContextの部分を切り出しています。
const defaultSharedRgbaValue: RGBA = { r: 0, g: 0, b: 0, a: 1 }
const SharedRgbaValueContext = createContext<RGBA>(defaultSharedRgbaValue)
const SharedRgbaDispatchContext = createContext<Dispatch<SetStateAction<RGBA>>>(() => undefined)
type Props = {
children: React.ReactNode
}
export const SharedRgbaContextProvider: React.FC<Props> = (props) => {
const [sharedRgb, setSharedRgb] = useState<RGBA>(defaultSharedRgbaValue)
return (
<SharedRgbaValueContext.Provider value={sharedRgb}>
<SharedRgbaDispatchContext.Provider value={setSharedRgb}>
{props.children}
</SharedRgbaDispatchContext.Provider>
</SharedRgbaValueContext.Provider>
)
}
export const useSharedRgbaValue = () => {
return useContext(SharedRgbaValueContext)
}
export const useSetSharedRgba = () => {
return useContext(SharedRgbaDispatchContext)
}
カスタムフックを作ってあるので、各コンポーネントではuseState
と同じような要領で使うことができます。
const [sharedRgba, setSharedRgba] = [useSharedRgbaValue(), useSetSharedRgba()]
動的なカラーの指定
スタイリングにはEmotionを使用しました。Emotionでは、スタイルを関数化することで動的にCSSを適用できます。これを使って、Contextで管理されたsharedRgba
が変更される度に表示する色を更新しています。
const IndexPage: React.FC = () => {
const sharedRgba = useSharedRgbaValue()
return (
<div>
<div css={() => colorBlockStyle(sharedRgba)} />
</div>
)
}
const colorBlockStyle = (rgba: RGBA) => css`
background-color: rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a});
`
カラーの更新
5つのカラーモードの中からHSLAを例にとって、カラーの更新の方法について書いていきます。
sharedRgba
①HSLA→HSLAのパラメーターを持つコンポーネント内部で、H, S, L, Aの値が変更された時に、ContextのsharedRgba
を更新します。
const ConverterHSLA: React.FC = () => {
const [sharedRgba, setSharedRgba] = [useSharedRgbaValue(), useSetSharedRgba()]
const [hsla, setHsla] = useState<HSLA>({ h: 0, s: 0, l: 0, a: 1 })
const [hslaText, setHslaText] = useState<string>("0, 0%, 0%, 1")
const onChangeH = (e: React.ChangeEvent<HTMLInputElement>) => {
const newHsla: HSLA = { h: parseInt(e.target.value), s: hsla.s, l: hsla.l, a: hsla.a }
setChanged(newHsla)
}
const setChanged = (newHsla: HSLA) => {
setHsla(newHsla)
setHslaText(toHslaText(newHsla))
const newRgba: RGBA = toRgbFromHsla(newHsla)
setSharedRgba({ ...newRgba, editedFrom: "Hsla" })
}
return (
<div>
<div>
<span>H</span>
<RangeInput min={0} max={360} step={1} value={hsla.h} onChange={onChangeH} />
<NumberInput min={0} max={360} step={1} value={hsla.h} onChange={onChangeH} />
</div>
{/* S, L, Aも同じ */}
<div>
<span>HSLA</span>
<TextInput value={hslaText} onChange={onHslaTextChange} />
</div>
</div>
)
}
<input type="range" />
でスライダーからの入力を、<input type="number" />
で100
のような値を、<input type="text" />
で"100, 80%, 50%, 1"
のようなひと繋がりの文字列を受け取るというように、3種類の方法で入力できます。
sharedRgba
→HSLA
②HexやRGBAなどのパラメーターを持つ外部のコンポーネントによってContextのsharedRgba
が更新された時、変更を検知して自身のコンポーネントに値を反映します。
//To hsla from sharedRgba
useEffect(() => {
if (sharedRgba.editedFrom === "Hsla") { return }
const newHsla = toHslaFromRgb(sharedRgba)
setHsla(newHsla)
setHslaText(toHslaText(newHsla))
}, [sharedRgba])
ポイントは、sharedRgba
にeditedFrom
プロパティを持たせ、どのカラーモードから値が変更されたか判別していることです。これによって、自身のパラメーターの変更によって再度自身が更新されることを防いでいます。
その他の工夫点
高い一覧性
すべてのカラーモードを一覧できるようにし、マウスクリック等でカラーモードを切り替える必要なく使えるようにしました。
入力とコピーをやりやすく
例えばHSLAであれば、H, S, L, Aそれぞれの値を調整できるだけでなく、"100, 80%, 50%, 1"
のような横一列での入力・コピーをできるようにしました。CSSと行ったり来たりしやすくするためです。
Tabキーでの移動
キーボードで快適に入力するために欠かせないのがTabキーでの移動です。
<input type="number" />
と<input type="text" />
にはtabIndex
を指定し、Tabキーを押した時に適切な順番でフォーカスされるようにしました。
逆に、<input type="range" />
にはtabIndex={-1}
を指定し、Tabキーを押してもフォーカスされないようにすることで、スムーズな数値の入力を邪魔しないように設計しました。
<RangeInput tabIndex={-1} />
<NumberInput tabIndex={5} />
<TextInput tabIndex={6} />
input
をクリックした際に全選択
const onFocus = (e: React.FocusEvent<HTMLInputElement>) => {
e.target.select()
}
塗り・文字色の両方で色を確認
人間の目は、面積が小さいと色が暗く見えてしまうので、塗り・文字色の両方で色を確認できるようにしました。
背景色を変更
カラーコードで背景色も変更できるようにしました。ダークモードでの見え方の検証などにご活用ください。
柔軟なテキストバリデーション
テキストのバリデーションを柔軟にし、input
からフォーカスが外れた時に入力値を正しい形式に直すようにしました。たとえばHSLで%
が抜けていている、数値が範囲を超えている、入力された文字が数字でない、などの場合にEnterを押すかフォーカスを外すとフォーマットされます。
快適に使えるカラー変換ツールを目指して工夫したので、ぜひ使ってみてください!
Discussion