4
2

[React×TypeScript] ColorPicker

Last updated at Posted at 2022-09-06

はじめに

初めまして、Goorerことyuichiroです。
最近は、Reactにどっぷりハマってしまっているので、useStateの勉強の為に作成した簡単なアプリについて書き残していきます。

作るもの

まずはどんなものを作るか見てみましょう。

作るもの

22092118178.gif

対象読者

Reactに興味があるまた、プログラミングに興味がある全ての方

環境

  • node.js 16.17.0
  • npm 8.15.0
  • create-react-app 5.0.1
  • react 18.2.0

create-react-app

まずは、こちらに詳しく記載されていますが新規にアプリを作成します。
真っ黒い画面を開き呪文を唱えます。
今回はアプリの名前を"color-picker"としていますが、お好きな名前を付けてください。

$ npx create-react-app color-picker --template typescript
$ cd colorPicker

次に再度呪文を唱えてアプリを一度起動してみます。

$ npm start

そうすると、localhost:3000で起動し自動的にブラウザが開きます。

ブラウザが開かない場合
(Macの方は、command|Windowsの方は、ctrl)+ターミナルのlocalhost:3000をクリック
もしくは、ブラウザに直接URLを打ち込んでください。

起動することの確認ができたので、終了します。
終了する時は、ctrl+Cをクリックします。

CodePenの場合 簡単にですが、CodePenの場合も紹介します。

まず、Penを作成します。(赤枠で囲ったところをクリック)
22092118.52.15.png

次に、ReactとTypescriptの設定をします。

設定動画

20921185540.gif

  • Pen Settings>js>JavaScript Preprocessor: TypeScript
  • Pen Settings>js>Add External Scripts/Pens: react 18.2.0
  • Pen Settings>js>Add External Scripts/Pens: react-dom 18.2.0
これで、アプリの骨格部分が出来ました。肉付けをしていきます。

RGBカラーモデル値入力機能

今回のアプリの肝となるRGBの入力機能を作成します。

その前に create-react-appで作成されたディレクトリ内の今回不要なファイル等を削除します。 次の呪文を唱えます。
~color-picker $ rm -rf public src README.md

その後、public/index.html src/Picker.tsx,picker.css を作成します。
またまた呪文を唱えます。

~color-picker $ mkdir public | touch public/index.html
~color-picker $ mkdir src | touch src/index.tsx | touch src/index.css | touch src/Picker.tsx | touch src/Picker.css

作成したファイルを以下の内容に更新します。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import Picker from './picker';
import './index.css';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <Picker />
  </React.StrictMode>
)
index.css
html, body, #root {
    height: 100%;
}
Picker.tsx
import './Picker.css';

function Picker() {
    return(
        <div className="box">
            <h1>Hello Picker🐶</h1>
        </div>
    );
}

export default Picker;
Picker.css
.box {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
}

スライダー作成

画面にスライダーを作成していきます。
Picker.tsxを以下のように修正します。

+const RgbValue = () => {
+    return(
+        <input id='slider' type='range' min='0' max='255' />
+    );
+};

function Picker() {
    return(
        <div className="box">
-            <h1>Hello Picker🐶</h1>
+            <RgbValue /> 
        </div>
    );
}

今回スライダーの最大値と最小値は、RGEに合わせています。
修正した内容は、HTMLの知識がほとんどの為解説は省略します。

手入力欄の作成

スライダーだけでは味気ないので、手入力欄も作成します。
Picker.tsxに以下を追記します。

const Slider = () => {
    return(
+       <div className={'input'}>
            <input id={'slider'} type={'range'} min={'0'} max={'255'} />
+          input id={'value'} type={'number'} min={'0'} max={'255'} value={''} />
+       </div>
    );
};

function Picker() {
    return(
        <div className="box">
            <Slider /> 
        </div>
    );
}

次に、入力欄に数値を入力できるようにします。
今回は関数コンポーネントなので、useStateを使用していきます。
useStateについては、こちらに詳しく書かれています。
簡単に説明すると、状態を保存しておくものだと思ってください。(雰囲気程度ですが。。)
それでは、Picker.tsxに以下を追記します。

+ import { useState } from 'react';

+ const RgbValue = (props:{colorName: string, colorValue: string}) => {
    return(
        <div className={'input'}>
+           <label htmlFor='slider'>{props.colorName}</label>
            <input id={'slider'} type={'range'} min={'0'} max={'255'} />
+           input id={'value'} type={'number'} min={'0'} max={'255'} value={props.colorValue} />
        </div>
    );
};

function Picker() {
    // const [状態を保存する変数, 状態を更新する関数] = React.useState(初期値)
+   const [red, setRed] = useState('0');
+   const [green, setGreen] = useState('0');
+   const [blue, setBlue] = useState('0');

    return(
        <div className="box">
+           <RgbValue colorName='R' colorValue={red} /> 
+           <RgbValue colorName='G' colorValue={green} /> 
+           <RgbValue colorName='B' colorValue={blue} /> 
        </div>
    );
}

ですが、このままでは入力欄の値を変更できません。なぜならuseStateを使用して宣言した
red,green,blueの値を更新していないからです。次はsetRed,setGreen,setBlueを使用して
値を更新できるようにします。
ここで数値のみが入力できるように追記します。

+ const RgbValue = (props:{colorName: string, colorValue: string, onChange:(e:React.ChangeEvent<HTMLInputElement>) => void}) => {
    return(
        <div className={'input'}>
            <label htmlFor='slider'>{props.colorName}</label>
            <input id={'slider'} type={'range'} min={'0'} max={'255'} />
+            <input id={'value'} type={'number'} min={'0'} max={'255'} value={props.colorValue} 
+            onChange={(e) => props.onChange(e)} />
        </div>
    );
};

function Picker() {
    // const [状態を保存する変数, 状態を更新する関数] = React.useState(初期値)
    const [red, setRed] = useState('0');
    const [green, setGreen] = useState('0');
    const [blue, setBlue] = useState('0');

+    const handleChangeRed = (e: React.ChangeEvent<HTMLInputElement>) => {
+        setRed(e.target.value);
+    };

+    const handleChangeGreen = (e: React.ChangeEvent<HTMLInputElement>) => {
+        setGreen(e.target.value);
+    };

+    const handleChangeBlue = (e: React.ChangeEvent<HTMLInputElement>) => {
+        setBlue(e.target.value);
+    };

    return(
        <div className="box">
+            <RgbValue colorName='R' colorValue={red} onChange={handleChangeRed} /> 
+            <RgbValue colorName='G' colorValue={green} onChange={handleChangeGreen} /> 
+            <RgbValue colorName='B' colorValue={blue} onChange={handleChangeBlue} /> 
        </div>
    );
}

入力欄の値の更新ができるようになりました。しかしまだ、rgbは0~255の範囲ですがそれ以上の数値が入力出来てしまいます。その点の制御をフォーカスアウトイベントを用いて追記します。

+ const RgbValue = (props:{colorName: string, colorValue: string, onChange: (e: React.ChangeEvent<HTMLInputElement>) => void, 
+ onBlur: (e: React.FocusEvent<HTMLInputElement>) => void}) => {
    return(
        <div className={'input'}>
            <label htmlFor='slider'>{props.colorName}</label>
            <input id={'slider'} type={'range'} min={'0'} max={'255'} />
+            <input id={'value'} type={'number'} min={'0'} max={'255'} value={props.colorValue} 
+            onChange={(e) => props.onChange(e)} onBlur={(e) => props.onBlur(e)} />
        </div>
    );
};

function Picker() {
    // const [状態を保存する変数, 状態を更新する関数] = React.useState(初期値)
    const [red, setRed] = useState('0');
    const [green, setGreen] = useState('0');
    const [blue, setBlue] = useState('0');

    const handleChangeRed = (e: React.ChangeEvent<HTMLInputElement>) => {
        setRed(e.target.value);
    };

    const handleChangeGreen = (e: React.ChangeEvent<HTMLInputElement>) => {
        setGreen(e.target.value);
    };

    const handleChangeBlue = (e: React.ChangeEvent<HTMLInputElement>) => {
        setBlue(e.target.value);
    };

+    const handleBlurRed = (e: React.FocusEvent<HTMLInputElement>) => {
+        setRed((0 <= parseInt(e.target.value) && parseInt(e.target.value) <= 255) ? e.target.value : '0');
+    }

+    const handleBlurGreen = (e: React.FocusEvent<HTMLInputElement>) => {
+        setGreen((0 <= parseInt(e.target.value) && parseInt(e.target.value) <= 255) ? e.target.value : '0');
+    }

+    const handleBlurBlue = (e: React.FocusEvent<HTMLInputElement>) => {
+        setBlue((0 <= parseInt(e.target.value) && parseInt(e.target.value) <= 255) ? e.target.value : '0');
+    }

    return(
        <div className="box">
+           <RgbValue colorName='R' colorValue={red} onChange={handleChangeRed} onBlur={handleBlurRed} /> 
+           <RgbValue colorName='G' colorValue={green} onChange={handleChangeGreen} onBlur={handleBlurGreen} /> 
+           <RgbValue colorName='B' colorValue={blue} onChange={handleChangeBlue} onBlur={handleBlurBlue} /> 
        </div>
    );
}

スライダーと入力欄の連携

ここでは、スライダーを動かした際に入力欄の値が連動するようにします。
大袈裟に書いていますが、今までに作成した関数を再利用するだけなので、ササっと追記してしまいます。

const RgbValue = (props:{colorName: string, colorValue: string, onChange: (e: React.ChangeEvent<HTMLInputElement>) => void, onBlur: (e: React.FocusEvent<HTMLInputElement>) => void}) => {
    return(
        <div className={'input'}>
            <label htmlFor='slider'>{props.colorName}</label>
+            <input id={'slider'} type={'range'} min={'0'} max={'255'} 
+            onChange={(e) => props.onChange(e)}/>
            <input id={'value'} type={'number'} min={'0'} max={'255'} value={props.colorValue} 
            onChange={(e) => props.onChange(e)} onBlur={(e) => props.onBlur(e)} />
        </div>
    );
};

カラー出力機能

最後に、出力の方を作成していきます。
Picker.tsxに新たな関数コンポーネントを作成します。

+ const Output = (props:{red: string, green: string, blue: string}) => {
+    return(
+        <div className='output'>
+            <span className='pallet' style={{backgroundColor:`rgb(${props.red},${props.green},${props.blue})`}}></span>
+            <input className='rgbText' type={'text'} value={`rgb(${props.red},${props.green},${props.blue})`} readOnly></input>
+        </div>
+    );
+ }

function Picker() {
  // 省略。。。
    return(
        <div className="box">
            <RgbValue colorName='R' colorValue={red} onChange={handleChangeRed} onBlur={handleBlurRed} /> 
            <RgbValue colorName='G' colorValue={green} onChange={handleChangeGreen} onBlur={handleBlurGreen} /> 
            <RgbValue colorName='B' colorValue={blue} onChange={handleChangeBlue} onBlur={handleBlurBlue} /> 
+            <Output red={red} green={green} blue={blue} />
        </div>
    );
}

最後の最後に今まで触れてこなかった、picker.cssを追記して終わりにしたいと思います。

.box {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
}

+ .box > .input {
+    margin: .5em;
+ }

+ .box > .output {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+ }

+ .pallet {
+    width: 50px;
+    height: 50px;
+    margin: 0.5rem;
+ }

+ .rgbText {
+    width: 100px;
+    height: 20px;
+ }

後半駆け足になってしまいましたが完成です!

全体像

See the Pen color-picker by yuichiro (@goorer) on CodePen.

さいごに

今回の一連の開発で、ReactのuseStateの使い方やReact×TypeScriptでのイベント型問題の理解ができました。
今後は、今回触れなかったuseState以外のhookにも挑戦していきたいです。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2