おはこんばんは。最近、Next.js(React)でアイコンのリサイズ機能を実装してみたのですが、意外とまとまった記事が無かったので実装方法をご紹介しようと思います!
今回はこんな感じのを実装します。サンプルはこちら。

実行環境
- next 10.0.9
- react 17.0.1
- react-modal 3.12.1
- react-avater 3.10.0
- react-avater-editor 11.1.0
- rc-slider 9.7.1
- pica 6.1.1
機能説明
今回の機能は主に以下となります。これを順に紹介していきます。
- アイコンの表示
- 画像のアップロード
- アイコンのプレビュー表示
- アイコンのサイズ・切り取り範囲の変更
アイコンの表示
まずは、最初の画面でアイコンを表示できるようにします。今回はreact-avater
を利用します。画像が無い際のデフォルト表示や画像の円形表示をしてくれるお手軽ライブラリです。
次に表示用とプレビュー用のStateを定義します。このStateで画像を管理します。
1const [icon, setIcon] = useState<File | null>(null);
2const [previewIcon, setPreviewIcon] = useState<File | null>(null);
そして以下みたいに利用すれば表示してくれます。ただ、まだ画像が無いので、デフォルト表示になるかと思います。
1<Avatar
2 size='160'
3 name="アイコン"
4 round
5 color="#ddd"
6 alt="アイコン"
7 src={icon ? URL.createObjectURL(icon) : ''}
8/>
画像のアップロード
では、早速画像のアップロードをできるようにします。今回はボタンを押されたら画像がアップロードされるようにします。
ページ上に表示されない input を以下のように定義し、ref を取得出来るようにします。
1const iconInputRef = useRef<HTMLInputElement | null>(null);
2
3<input
4 type="file"
5 accept="image/*"
6 style={{ display: 'none' }}
7 ref={iconInputRef}
8 onChange={handleChangePreviewIcon}
9/>
先ほど、取得した inputのref がボタンのクリックイベントでクリックされるようにします。これで画像選択が可能になります。
1const handleClickChangeIcon = useCallback(() => {
2if (!iconInputRef || !iconInputRef.current) return;
3iconInputRef.current.click();
4}, []);
5
6<button
7 type="button"
8 onClick={handleClickChangeIcon}
9>
10
最後にプレビューのStateを選択された画像を更新するようにします。e.currentTarget.value = "''
をしているのは、同じ画像を連続で選択された際、イベントが実行されないので、valueをリセットして再実行可能とするためです。
1const handleChangePreviewIcon = useCallback(
2 (e: ChangeEvent<HTMLInputElement>) => {
3 if (!e.target.files?.length) return;
4 setPreviewIcon(e.target.files[0]);
5 e.currentTarget.value = '';
6 },
7 [],
8);
アイコンのプレビュー表示
プレビューの表示では、react-modal
を利用します。お手軽にモーダルが実装できるライブラリです。
previewIcon
に値がセットされたらモーダルが開くようにしています。
./components/IconEditor.tsx
1<Modal
2 isOpen={!!previewIcon}
3 onRequestClose={handleCloseIsOpen}
4 ariaHideApp={false}
5 overlayClassName={{
6 base: styles.overlayBase,
7 afterOpen: styles.overlayAfter,
8 beforeClose: styles.overlayBefore,
9 }}
10 className={{
11 base: styles.contentBase,
12 afterOpen: styles.contentAfter,
13 beforeClose: styles.contentBefore,
14 }}
15 closeTimeoutMS={500}
16>
17 <div className={styles.header}>
18 </div>
19</Modal>
アイコンのサイズ・切り取り範囲の変更
画像のリサイズにはreact-avater-editor
とrc-slider
を利用します。
1yarn add react-avater-editor rc-slider
AvatarEditor
ではrefを渡すことでリサイズした画像データを取得出来ます。scaleで画像の拡大が可能です。その調整はrc-slider
を用いて行います。
./components/IconEditor.tsx
1const editorRef = useRef<AvatarEditor | null>(null);
2const [scale, setScale] = useState(1);
3
4<AvatarEditor
5 ref={editorRef}
6 image={previewIcon ? URL.createObjectURL(previewIcon) : ''}
7 width={ICON_WIDTH}
8 height={ICON_HEIGHT}
9 borderRadius={100}
10 color={[0, 0, 0, 0.6]}
11 scale={scale}
12 rotate={0}
13/>
14
15<Slider
16 onChange={handleChangeScale}
17 min={1}
18 max={1.5}
19 step={0.01}
20 value={scale}
21/>
22
ただ、AvatarEditor
のwidthとheightがコンポーネントのサイズになるのですが、これが実際にリサイズされる画像の大きさになります。これを必ずしもそうしたくないケースがありますよね。そういった場合は、pica
を利用します。
1 const handleClickFileSave = useCallback(async () => {
2 if (!editorRef.current) return;
3
4 const img = editorRef.current.getImage();
5 const canvas = editorRef.current.getImageScaledToCanvas();
6 canvas.width = ICON_WIDTH;
7 canvas.height = ICON_HEIGHT;
8 const picaCanvas = await pica().resize(img, canvas, { alpha: true });
9
10 picaCanvas.toBlob((blob) => {
11 const nextFile = new File([blob], previewIcon.name, {
12 type: previewIcon.type,
13 lastModified: Date.now(),
14 });
15 onChangeIcon(nextFile);
16 handleCloseIsOpen();
17 });
18
19 }, [previewIcon, onChangeIcon]);
これで期待する画像にリサイズが出来ます。お疲れ様でした。
さいごに
今回は、Next.jsでアイコンのリサイズ機能を実装する方法をご紹介しました!Reactは色々とライブラリがあって便利ですね。今回の記事が誰かの参考になれば幸いです。