Firebaseを使わずにブラウザ上でローカル画像をリサイズ【canvasのdrawImage()をReactで利用した方法】

Firebase側で画像のサイズを変えることはできますがFirebase側への通信量を抑えたいと思ったのでブラウザ上でサイズを小さくすることにしたことのまとめです。
プロフィール画像を想定していて画像の最大幅がある程度は分かっていた流れがあります。

## 成果物
![スクリーンショット 2021-05-14 17.56.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651103/5e39b0ac-5da1-b044-61e3-eeaffbb5a71b.png)
アップロードされた画像は canvas の drawImage() でリサイズされたものです。


## ソースコード

```App.js
import { useState } from "react";
import "./styles.css";

const uploadToFirestore = () => {
  console.log("画像をuploadしました"); //ダミー処理
};

const App = () => {
  const [uploadImg, setUploadImg] = useState("");

  const handleUploadImg = (e) => {
    if (!e.currentTarget.files || e.currentTarget.files.length === 0) return;

    const fileReader = new FileReader();
    const file = e.currentTarget.files[0];
    if (file.type !== "image/jpeg" && file.type !== "image/png") return;

    fileReader.onloadend = () => {
      const fileImage = new Image();
      fileImage.onload = () => {
        const uploadWidth = fileImage.width,
          uploadHeight = fileImage.height;

        const wantWidth = uploadWidth < 200 ? uploadWidth : 200,
          resizeRatio = uploadWidth / wantWidth,
          wantHeight = uploadHeight / resizeRatio;

        const canvas = document.createElement("canvas");
        canvas.width = wantWidth;
        canvas.height = wantHeight;
        let ctx = canvas.getContext("2d");
        ctx.drawImage(fileImage, 0, 0, wantWidth, wantHeight);
        ctx.canvas.toBlob(
          async (blob) => {
            const resizedImageFile = new File([blob], file.name, {
              type: file.type,
              lastModified: Date.now()
            });
            const blobToUrl = URL.createObjectURL(blob); //blobをimgのsrc属性で使える形へ変換
            setUploadImg(blobToUrl); //リサイズした画像を表示
            uploadToFirestore(resizedImageFile); //resizedImageFileをFirestoreへ
          },
          file.type,
          1
        );
      };
      fileImage.src = fileReader.result;
    };
    fileReader.readAsDataURL(file);
  };

  return (
    <div className="App">
      <h1>React画像アップロード</h1>
      <input type="file" onChange={(e) => handleUploadImg(e)} />
      <div style={{ margin: "10px" }}>
        <img src={uploadImg} alt="アップロード画像" />
      </div>
    </div>
  );
};

export default App;

```
codesandbox URL: https://codesandbox.io/s/react-img-size-change-canvas-n1iln?file=/src/App.js:0-1967

## コード解説 概要
・FileReaderインスタンスでアップロードした画像を取り出す
・Imageインスタンスを呼び出しcanvasメソッドを使ってリサイズする
・fileImage.onloadの中身のコードについて

### FileReaderインスタンスでアップロードした画像を取り出す

```App.js
// 省略

const fileReader = new FileReader();
const file = e.currentTarget.files[0];

// 省略

fileReader.readAsDataURL(file);
```
FileReaderインスタンスであるfileReaderの中のreadAsDataURL(file)を読むことでfileReder.onloadを発火させます。

ここの詳細については前回の記事、<a href="https://qiita.com/akifumiyoshimu/items/0979e2de508c63d473d9">Reactで画像アップロード機能を実装してローカルファイルを読み込ませる</a>で書いています。

### Imageインスタンスを呼び出しcanvasメソッドを使ってリサイズする

```App.js
// 省略

 const fileImage = new Image();
      fileImage.onload = () => {
      // 省略
  }
  fileImage.src = fileReader.result;

// 省略
}
```
ImageインスタンスであるfileImageのsrcに画像データ（blobデータ）を代入するとonloadが走ります。
ちなみにfileReader.resultの中身を imgタグ の src属性 に入れても読み込むことができます。

### fileImage.onloadの中身のコードについて

```App.js
// 省略
      fileImage.onload = () => {
        //読み込んだ画像からサイズを取り出す
        const uploadWidth = fileImage.width,
          uploadHeight = fileImage.height;

        //幅200を超える画像は全て200へリサイズする。そうでない場合はリサイズしない。wnatWidthはリサイズしたいサイズのこと。
        const wantWidth = uploadWidth < 200 ? uploadWidth : 200,
          resizeRatio = uploadWidth / wantWidth,
          wantHeight = uploadHeight / resizeRatio;

        //canvasメソッドを使ってサイズ指定してdrawImage(略)でレンダリング
        const canvas = document.createElement("canvas");
        canvas.width = wantWidth;
        canvas.height = wantHeight;
        let ctx = canvas.getContext("2d");
        ctx.drawImage(fileImage, 0, 0, wantWidth, wantHeight);

　　　　 //canvasデータをblobデータへ変換し、受け取った結果を元にイメージファイルであるresizedImageFile変数を作る
        ctx.canvas.toBlob(
          async (blob) => {
            const resizedImageFile = new File([blob], file.name, {
              type: file.type,
              lastModified: Date.now()
            });

            const blobToUrl = URL.createObjectURL(blob); //blobをimgのsrc属性で使える形へ変換
            setUploadImg(blobToUrl); //リサイズした画像を表示
            uploadToFirestore(resizedImageFile); //resizedImageFileをFirestoreへ
          },
          file.type,
          1
        );
      };
// 省略
```
コードへ注釈を入れていますがそのままの通りになります。