画像をアップロード前に圧縮する流行りの方法【Vue.js x Firebase x 令和】

こんにちは。年末年始はずっとFirebaseを触っていました @ykhirao です。たぶん3年くらい遅れて流行に乗り始めています。Typescriptお正月にはじめました。

今日は、仕事がお休みなのでスマホぽちぽちしていたらすごく参考になる記事を見かけましたのですが、こちらの記事 [Firebaseで作る！リアルタイム画像変換CDN【Firebase Hosting + Cloud Functions】 - AppBrew Tech Blog](https://tech.appbrew.io/entry/2020/01/15/120000) で書かれている `アップロードされた画像をそのまま表示する時代は平成とともに終わりを告げたわけですが[※要出典]` という文言を見てくすっと笑ってしまい、少し前に [君はまだ平成のアーキテクチャを使ってるのか？僕はFirebaseと令和の時代に行くぞ。](https://qiita.com/hecateball/items/c55b6811835923fb9574) という記事がたくさんの方に読まれていたこととか、Twitterの擬人化された [#令和ちゃん](https://twitter.com/search?q=%23令和ちゃん) を思い出したりして、エンジニアのみなさんは令和ちゃんもFirebaseも大好きっぽいので私も記事を書こうかなーーと思って書き始めました。ネタなんだけど誰かの役にたつ記事を書きたい次第です。


さて前置きが長くなりましたが、本文は、画像をアップロードする前にゴリッと処理する方法を書いて行きます。

# 画像をアップロード前に圧縮する流行りの方法

## 今回紹介すること、しないこと

```
# 画像アップロードについての手法

1. 画像処理をしない(平成)

2. 画像処理をする(令和)
  - 2.1 サーバーで処理する (https://tech.appbrew.io/entry/2020/01/15/120000 で紹介されている)
    - Firebase Extension（まだβっぽいですが公式の拡張があるっぽい！）
    - CloudFunctionsを自分でNode.JSごりごり書く
  - 2.2 クライアント側で処理する( <---- 今日の記事はここ！！！)
```



## 本文いらないからスクショとかGistみたいひと

以下のスクショのようになるGistはこちら
https://gist.github.com/ykhirao/1d36aeca9abb02709cc9dd3d676040ef

<img width="1672" alt="スクリーンショット 2020-01-16 13.32.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112929/b1537517-ec44-f9b0-3f24-0f99b7cea44a.png">

<img width="1330" alt="スクリーンショット 2020-01-16 14.17.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112929/d6d7f09d-fdc6-479a-3f96-7e9a71260be2.png">

(最大 100x100 px にしているので2kbくらい)


## 処理して簡単に解説する

[Canvasでごりっと処理する](https://qiita.com/komakomako/items/8efd4184f6d7cf1363f2)とか、ライブラリに頼るとか方法はあると思いますが今回は [compressorjs](https://github.com/fengyuanchen/compressorjs) を紹介したいっす。

```component.js
import Compressor from 'compressorjs'
```

OR

```index.html
<script src="https://cdn.jsdelivr.net/gh/fengyuanchen/compressorjs/dist/compressor.min.js"></script>
```

CDNはなかなか見つからなかったのですがぐぐったら [このブログ](https://paiza.hatenablog.com/entry/2019/04/03/画像圧縮やオートコンプリートなどWeb開発を簡単に) で見つかった。まさとらんさんありがとうです。

画像は `input` タグで選択してもらって

```js
<input
  type="file"
  id="file"
  multiple
  accept="image/*"
  @change="handleFiles"
/>
```

`multiple` で複数選択可にして `accept` で画像以外を一応弾いています。それで `@change` イベントのフックで画像を圧縮しています。

```js
handleFiles(e: Event) {
  const images = this.images
  const files: FileList | null = (<HTMLInputElement>e.target).files
  if (files === null) return
  ;[...files].forEach((file: File) => {
    // 複数ファイルを受け入れているので、この中で処理している
  })
}
```

複数処理やfileが取得できなかった場合の処理は上のような感じで、Typescript的にエラーが出ていないのでこれでOKかなと思ってます（認識あってますか？）

```js
const payload: Compressor.Options = {
  quality: 0.8,
  maxWidth: 100,
  maxHeight: 100,
  mimeType: 'image/jpeg',
  success(blob: Blob): void {
    // ここに成功時の処理を書く。次。
  },
  error(err: Error): void {
    console.log(err.message)
  }
}
new Compressor(file, payload)
```

実際の処理はこんな感じで横幅・縦幅の最大とか圧縮率とかたくさんの [オプション](https://github.com/fengyuanchen/compressorjs/blob/master/README.md#options) があります。また成功時と失敗時は `success` と `error` のコールバックに書くみたいです。


```js
success(blob: Blob): void {
},

```

サクセスのコールバックは `Blob` を受け取るのでFirebaseではこの段階でファイルのアップロードができます！！！[ドキュメントはこちら。](https://firebase.google.com/docs/storage/web/upload-files?hl=ja#upload_from_a_blob_or_file)  Blobからアップロードできるとかすごくいいですね。(試してないけどたぶんそちらでUploadできる)

今回はアップロードする前に読み込みを確認したかったので、DataUrlという形式に変換しています。

```js
success(blob: Blob): void {
  var reader = new FileReader()
  reader.onloadend = () => {
    const result = <string | ArrayBuffer | null>reader.result
    if (result instanceof ArrayBuffer || result === null) return
    images.push(result)
  }
  reader.readAsDataURL(blob)
},
```

`images.push(result)` する段階でDataUrlという形式の `data:image/jpeg;base64,XXXXXXXX`みたいな文字列になっています。[ドキュメントの文字列からアップロードする](https://firebase.google.com/docs/storage/web/upload-files?hl=ja#upload_from_a_string)を見ると

```js
var message = 'data:text/plain;base64,5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB';
ref.putString(message, 'data_url').then(function(snapshot) {
  console.log('Uploaded a data_url string!');
});
```

これでアップロードできそうですね。

ドキュメントを読むと `data:text/plain;base64,` という文字列を削除したら ref.putString(message, 'base64url')でアップロードできそうです。たぶん。

サンプルファイルは `<script lang="ts">` にしているのでVSCodeでひらいて、 `CMD + Click` とかで定義元とか飛んで行って自分で触ってみてください！！

<img width="659" alt="スクリーンショット 2020-01-16 14.09.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112929/8d08379f-2192-bbf1-8b84-d727d73ab27b.png">

## 終わりに

ブラウザのAPIを叩くときにTypeScriptすごく開発体験良かったです。MDNを見に行かなくても何が返ってくるかわかるので、null制御とかそういうのがやりやすかったです。コード書いてて楽しかった。

なにか間違い見つけたら編集リクエストください！！！

最後まで読んでいただきありがとうございます。！！