html2canvasを使ってVue.jsで画面キャプチャを取る（おまけ付き）

[html2canvas](https://html2canvas.hertzen.com/)

Vue.js + TypeScriptの構成で画面キャプチャ取りたいな〜と思っていたら良さそうなライブラリを見つけました。  
これを使って一応要求は満たせし、せっかくなので実装例とかを書き残してみよう思います。
ちなみにこういうのもあるんですが、  
[vue-html2canvas](https://github.com/mycurelabs/vue-html2canvas)  
型定義ファイルが用意されてなかったのと、軽く中身を見てみたところ、あえて利用するメリットもなさそうな感じだったので普通に本家を使おうと思いました。  

## dataURLで表示する  
  
`html2canvas` と型定義ファイルをインストールしておきます。  
まずはオーソドックスに指定した領域のキャプチャを取って画面に表示してみます。

```vue

<template>
  <div>
    <div @click="capture">
      ここを押したら
    </div>
    <div ref="capture">
      この領域を
    </div>
    <div>
      ここ↓に出す
      <img :src="dataURL"/>
    </div>
  </div>
</template>

<script lang="ts">
import html2canvas from "html2canvas";

...中略。以下クラス構文で書きます

  dataURL: string = "";

  async capture() {
    const el = this.$refs.capture as HTMLElement;
    const params: Parameters<typeof html2canvas> = [
      el,
      {
        // オプションを指定、一旦飛ばします
      }
    ]
    const canvasElement = await html2canvas(...params).catch(e => {
      console.error(e);
      return;
    });
    if (!canvasElement) {
      return;
    }
    this.dataURL = canvasElement.toDataURL();
  }
</script>
```

ライブラリを使うときutilityTypesのParametersが地味に便利ですね。  

これでキャプチャを取って画面に表示させることができました。  

`options` にプロパティを指定できるので主に今回個人的に利用してみたものについて見ていこうと思います。

- `x`
  - キャプチャ領域のx座標開始位置（領域左端）をnumberで指定します。単位はピクセルです。
- `y`
  - `x`と同様、こちらはy軸の開始位置です。
- `width`
  - キャプチャ領域の横幅です。画面サイズをオーバーしてしまう場合（今回そうだった）などは、ここの数値をウィンドウサイズより幅を多めに取ることで見切れないようにしてあげる必要があります。 
- `height` 
  - `width`と同様こちらは高さ幅です。  
- `ignoreElements`  
  - こちらはコールバック用のメソッドを指定します。特定の条件ではキャプチャを取りたくない場合には `false`を返してあげることでスキップできます。  


### Blobがほしい  

dataURLではなくBlobが欲しい場合は専用のメソッドが生えています。  
今回の実装では画像をリサイズしたかったのでBlobに変換する必要がありました。

```ts
...中略

    canvasElement.toBlob(async blob => {
      if (!blob) {
        return;
      }
      console.log(blob, "blobです")

      // Fileオブジェクトが欲しければこうですね。
      const file = new File([blob], filename, {
        type: mimetype
      });
    }
```

シンプルに使えて良いライブラリでした。  
意図したキャプチャが取れない場合は、[ドキュメント](https://html2canvas.hertzen.com/configuration) を参照して、オプションの指定を変えつつ試してみてください。  

## おまけ  

今回firebaseのストレージにキャプチャを送信したかったのです。dataURLからなら、  

```js
const dataURL = canvasElement.toDataURL();
await storageReference.putString(dataURL, 'data_url')
```

で送信できますが、案外ファイルサイズが大きかったのです。(`options`の`scale`を調整してみましたがうまくいかず...)  

そこで別ライブラリの [compressorjs](https://github.com/fengyuanchen/compressorjs)の力を借りて適当な大きさにリサイズすることにしました。  

```ts
import Compressor from "compressorjs";

  getCompressedCapture(blob: Blob, width: number) {
    return new Promise<Blob>(resolve => {
      new Compressor(blob, {
        width,
        success: compressed => {
          resolve(compressed);
        },
        error(err) {
          throw err;
        }
      });
    });
  }
``` 

先程の `canvasElement`を`toBlob()`して得られた結果と、縮小したい横幅ピクセルを引数に指定してあげればリサイズ後のBlobが取れるやつです。  

適当にかぶらない日時データを使ってファイル名を付けてあげれば準備は万全です。firebaseに送りつけます。  

```ts
import firebase from "firebase/app";
import "firebase/storage";
import html2canvas from "html2canvas";
import Compressor from "compressorjs";
import { DateTime } from "luxon";

...中略

  async sendCapture() {
    const el = this.$refs.capture as HTMLElement;
    const format = "png";
    const maxCaptureWidth = 1000;
    const params: Parameters<typeof html2canvas> = [
      el,
      {
        // options
      }
    ];
    const canvasElement = await html2canvas(...params).catch(e => {
      console.error(e);
      return;
    });
    if (!canvasElement) {
      return;
    }
    canvasElement.toBlob(async blob => {
      if (!blob) {
        console.error("error");
        return;
      }
      const compressed = await this.getCompressedCapture(blob, maxCaptureWidth);
      const filename = [DateTime.local().toFormat("x"), format].join(".");
      const storageRef: firebase.storage.Reference = firebase
        .storage()
        .ref()
        .child(`pathToDir/${filename}`);
      await storageRef.put(compressed).catch(e => {
        console.error(e);
      });
    });
  }
```  

これで任意サイズの画面キャプチャを時系列でfirebaseに保存できます。  
firebase大好きです。