ブラウザ側でサイズ圧縮(リサイズ)して画像表示やアップロードを行う(Vue.js)

# 概要
Webアプリでクライアントサイド(ブラウザ)で画像を圧縮(リサイズ)する方法の紹介です。
サンプルとして下図のような入力した画像をプレビュー表示した上で、確定時にサーバーに画像をアップロードするまでのサンプルコードを紹介します。

※今回はvue.jsとvuetifyによるサンプルになりますが、UIのレイアウト部分以外は基本的に一般的なjavascirptですので
他のフレームワークを使用している場合等でも参考にはなるかと思います

![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/68354ce3-f242-74ba-7e3a-8a5d9bdc83ec.png)

動くデモとGitHubのサンプルコードは下記です。
[Demo](https://nobu17.github.io/vue_image_upload_sample/)
[GitHub](https://github.com/nobu17/vue_image_upload_sample)


# 背景
Webアプリで、画像ファイルをアップロードするといったシチュエーションはよくあると思います。
スマホ等で撮影した写真等だと、解像度やファイルサイズも大きいため、そのまま使用せず一度リサイズしたりする事が多いかと思います。
アップロード後にサーバ側で処理するといった手法もありますが、今回はWEBブラウザ側でリサイズする方法とします。


# 実現方法
当初はCanvasにリサイズした画像を描画して、再度データ化する方法を使用していました。
[参考](https://qiita.com/komakomako/items/8efhad4184f6d7cf1363f2)

しかしながら、もっとお手軽にかつファイルサイズも小さくしてアップできる、[Browser Image Compression](https://github.com/Donaldcwl/browser-image-compression) といったものがあるのでそちらを使用します。


## 画面レイアウト
以下のような、3つの領域を作成します。

- 画像のプレビュー領域
　入力した画像のプレビュー表示します。サンプルではvuetifyのv-imgタグを使用していますが、imgタグに相当するもの配置します。合わせてファイル名も表示します。

- 画像の入力
　ファイルを入力するinputタグです。ファイル選択対アログで画像を選択するとプレビュー領域に表示されます。
- Submitボタン
　プレビュー表示した画像をサーバにアップロードするためのボタン。画像入力中(リサイズ中)はプレビュー表示されるまでは押せなくします。


これらをvue.jsのコンポーネントで書くと下記のようになります。
処理等は無く、まだレイアウトだけです。

```ImageUpload.vue
<template>
  <v-container>
    <v-layout text-xs-center wrap>
      <v-flex xs12>
        <!-- 画像のプレビュー表示領域 -->
        <v-img :src="upimage.fileUrl" aspect-ratio="2" :contain="true"></v-img>
        <p>{{ upimage.fileName }}</p>
        <p>圧縮前サイズ(MB):{{ fileInfo.before.size }}</p>
        <p>圧縮後サイズ(MB):{{ fileInfo.after.size }}</p>
      </v-flex>
      <v-flex xs12>
        <!-- ファイルの選択 -->
        <input @change="selectedFile" type="file" accept="image/jpeg, image/jpg, image/png">
      </v-flex>
      <v-flex xs12>
        <!-- Submitボタン -->
        <v-btn color="primary" :disabled="isUploading">submit</v-btn>
      </v-flex>
    </v-layout>
  </v-container>
</template>
<script>
export default {
  data() {
    return {
      isUploading: false, // 画像ファイルアップロード中の判断フラグ
      upimage: { fileUrl: "", fileName: "", blob: null } // 画像ファイル
    };
  },
  methods: {
    async selectedFile(e) {
      // ファイル入力時の処理 
      // e.target.filesにファイルの情報が格納
    },
    async submit() {
      // 画像をサーバに送信する処理
    }
  }
};
</script>
```

## 画面のリサイズ
入力された画像をプレビュー表示前にリサイズする処理を作成します。
まずは、npmかyarnでbrowser-image-compressionをインストールします。

```cmd
npm install browser-image-compression --save
or
yarn add browser-image-compression
```

次に以下の2つの処理を作成します。

1. 入力された画像ファイルの圧縮を行う処理
2. プレビュー表示用にDataUrlを取得する処理

DataUrlにするのはimgタグにおいてプレビュー表示を行うためですので、直接アップロードしたい場合は不要です。
画像の圧縮時には最大のファイルサイズおよび、解像度を指定します。
今回は最大サイズは1MB, 解像度を800に指定しています。
その他にもオプションがありますので、詳細はbrowser-image-compressionのGitHubを参照してください。

```ImageUtil.js
import imageCompression from "browser-image-compression";

export default {
  // アップロードされた画像ファイルを取得
  async getCompressImageFileAsync(file) {
    const options = {
      maxSizeMB: 1, // 最大ファイルサイズ
      maxWidthOrHeight: 800 // 最大画像幅もしくは高さ
    };
    try {
      // 圧縮画像の生成
      return await imageCompression(file, options);
    } catch (error) {
      console.error("getCompressImageFileAsync is error", error);
      throw error;
    }
  },
  // プレビュー表示用のdataurlを取得
  async getDataUrlFromFile(file) {
    try {
      return await imageCompression.getDataUrlFromFile(file);
    } catch (error) {
      console.error("getDataUrlFromFile is error", error);
      throw error;
    }
  }
};

```

## 画面への処理の取り込み
この画像処理を先ほどのレイアウト内で呼び出せば終了です。

```ImageUpload.vue
// 略
<script>
import ImageUtil from "../lib/imageUtil";
export default {
  // 略
  methods: {
    async selectedFile(e) {
      this.isUploading = true;

      const file = e.target.files[0];
      if (!file) {
        return;
      }

      try {
        // 圧縮した画像を取得
        const compFile = await ImageUtil.getCompressImageFileAsync(file);

        //ファイルサイズの表示
        this.fileInfo.before.size = (file.size / 1024 / 1024).toFixed(4);
        this.fileInfo.after.size = (compFile.size / 1024 / 1024).toFixed(4);
        // 画像情報の設定
        this.upimage.blob = compFile;
        this.upimage.fileUrl = await ImageUtil.getDataUrlFromFile(compFile);
        this.upimage.fileName = file.name;
      } catch (err) {
        // エラーメッセージ等を表示
      } finally {
        this.isUploading = false;
      }
    },
    submit() {
      const fd = new FormData();
      try {
        fd.append(this.upimage.fileName, this.upimage.blob, this.upimage.fileName);
        // ここにサーバーへのアップロード処理を実装する
      } catch (err) {
        // エラーメッセージ等を表示
      }
    }
  }
};
</script>

```


## まとめ
ということで、browser-image-compressionを使うことで、お手軽に画像の圧縮及び表示が可能となります。
是非試してみてください。