ブログトップ画像

Next.js(React)でアイコンのリサイズ機能をサクッと実装する方法

フロントエンド

おはこんばんは。最近、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


機能説明

今回の機能は主に以下となります。これを順に紹介していきます。

  1. アイコンの表示
  2. 画像のアップロード
  3. アイコンのプレビュー表示
  4. アイコンのサイズ・切り取り範囲の変更


アイコンの表示

まずは、最初の画面でアイコンを表示できるようにします。今回はreact-avaterを利用します。画像が無い際のデフォルト表示や画像の円形表示をしてくれるお手軽ライブラリです。

yarn add react-avatar


次に表示用とプレビュー用のStateを定義します。このStateで画像を管理します。

./page/index.tsx

const [icon, setIcon] = useState<File | null>(null);
const [previewIcon, setPreviewIcon] = useState<File | null>(null);


そして以下みたいに利用すれば表示してくれます。ただ、まだ画像が無いので、デフォルト表示になるかと思います。

./page/index.tsx

<Avatar
 size='160'
 name="アイコン"
 round
 color="#ddd"
 alt="アイコン"
 src={icon ? URL.createObjectURL(icon) : ''}
/>


画像のアップロード

では、早速画像のアップロードをできるようにします。今回はボタンを押されたら画像がアップロードされるようにします。

ページ上に表示されない input を以下のように定義し、ref を取得出来るようにします。

./page/index.tsx

const iconInputRef = useRef<HTMLInputElement | null>(null);

<input
  type="file"
  accept="image/*"
  style={{ display: 'none' }}
  ref={iconInputRef}
  onChange={handleChangePreviewIcon}
/>


先ほど、取得した inputのref がボタンのクリックイベントでクリックされるようにします。これで画像選択が可能になります。

./page/index.tsx

const handleClickChangeIcon = useCallback(() => {
if (!iconInputRef || !iconInputRef.current) return;
iconInputRef.current.click();
}, []);

<button
  type="button"
  onClick={handleClickChangeIcon}
>


最後にプレビューのStateを選択された画像を更新するようにします。e.currentTarget.value = "''をしているのは、同じ画像を連続で選択された際、イベントが実行されないので、valueをリセットして再実行可能とするためです。

./page/index.tsx

const handleChangePreviewIcon = useCallback(
  (e: ChangeEvent<HTMLInputElement>) => {
    if (!e.target.files?.length) return;
    setPreviewIcon(e.target.files[0]);
    e.currentTarget.value = '';
  },
  [],
);


アイコンのプレビュー表示

プレビューの表示では、react-modalを利用します。お手軽にモーダルが実装できるライブラリです。

yarn add react-modal


previewIconに値がセットされたらモーダルが開くようにしています。

./components/IconEditor.tsx

<Modal
  isOpen={!!previewIcon}
  onRequestClose={handleCloseIsOpen}
  ariaHideApp={false}
  overlayClassName={{
    base: styles.overlayBase,
    afterOpen: styles.overlayAfter,
    beforeClose: styles.overlayBefore,
  }}
  className={{
    base: styles.contentBase,
    afterOpen: styles.contentAfter,
    beforeClose: styles.contentBefore,
  }}
  closeTimeoutMS={500}
>
  <div className={styles.header}>
  </div>
</Modal>


アイコンのサイズ・切り取り範囲の変更

画像のリサイズにはreact-avater-editorrc-sliderを利用します。

yarn add react-avater-editor rc-slider


AvatarEditorではrefを渡すことでリサイズした画像データを取得出来ます。scaleで画像の拡大が可能です。その調整はrc-sliderを用いて行います。

./components/IconEditor.tsx

  const editorRef = useRef<AvatarEditor | null>(null);
 const [scale, setScale] = useState(1);

<AvatarEditor
  ref={editorRef}
  image={previewIcon ? URL.createObjectURL(previewIcon) : ''}
  width={ICON_WIDTH}
  height={ICON_HEIGHT}
  borderRadius={100}
  color={[0, 0, 0, 0.6]}
  scale={scale}
  rotate={0}
/>

<Slider
  onChange={handleChangeScale}
  min={1}
  max={1.5}
  step={0.01}
  value={scale}
/>


ただ、AvatarEditorのwidthとheightがコンポーネントのサイズになるのですが、これが実際にリサイズされる画像の大きさになります。これを必ずしもそうしたくないケースがありますよね。そういった場合は、picaを利用します。

yarn add pica


  const handleClickFileSave = useCallback(async () => {
    if (!editorRef.current) return;

    const img = editorRef.current.getImage();
    const canvas = editorRef.current.getImageScaledToCanvas();
    canvas.width = ICON_WIDTH;
    canvas.height = ICON_HEIGHT;
    const picaCanvas = await pica().resize(img, canvas, { alpha: true });

    picaCanvas.toBlob((blob) => {
      const nextFile = new File([blob], previewIcon.name, {
        type: previewIcon.type,
        lastModified: Date.now(),
      });
      onChangeIcon(nextFile);
      handleCloseIsOpen();
    });

  }, [previewIcon, onChangeIcon]);


これで期待する画像にリサイズが出来ます。お疲れ様でした。

さいごに

今回は、Next.jsでアイコンのリサイズ機能を実装する方法をご紹介しました!Reactは色々とライブラリがあって便利ですね。今回の記事が誰かの参考になれば幸いです。