LoginSignup
79
84

More than 3 years have passed since last update.

画像をリサイズしてblobでプレビュー表示する方法【Vue/Canvas】

Last updated at Posted at 2019-05-22

ユーザーが画像をアップロードした際に、サーバー側へPOSTする前に
クライアント側で(容量が大きい場合)リサイズし、プレビュー表示したかったので実装しました

2019.6.4 追記
ライブラリの使用が可能なら下記記事のほうが簡単に出来ます
* 【簡単】画像選択時のプレビュー表示とExif対応【JavaScript-Load-Image/Vue】 - Qiita

やっていること

  • 画像をupload
  • 内部でリサイズ
  • base64からblobへトランスコード
  • プレビュー表示

実際の挙動はこんな感じです!

ezgif.com-video-to-gif.gif

実際のコード

template部分

index.vue
<template>
  <div class="resize-img">
    <!-- 画像選択 -->
    <div v-show="!resizedImg" class="resize-img__post">
      <label for="file" class="resize-img__post__label">画像
        <input
          id="file"
          ref="fileInput"
          type="file"
          accept=".jpeg, .png"
          @change="uploadImg">
      </label>
    </div>
    <!-- プレビュー -->
    <div v-show="resizedImg" class="resize-img__preview">
      <div class="resize-img__preview__circle" @click="clearUploadImg">
        <span class="resize-img__preview__circle__close-icon">×</span>
      </div>
      <canvas ref="canvas" class="resize-img__preview__canvas"/>
    </div>
  </div>
</template>

script部分

index.vue

<script>
export default {
  data() {
    return {
      resizedImg: null
    };
  },
  destroyed() {
    this.clearUploadImg();
  },
  methods: {
    uploadImg(e) {
      const file = e.target.files[0];
      const reader = new FileReader();
      reader.onload = (e) => {
        this.generateImgUrl(e.target.result);
      };
      reader.readAsDataURL(file);
    },
    generateImgUrl(file) {
      const image = new Image();
      image.crossOrigin = 'Anonymous';

      image.onload = (e) => {
        const resizedBase64 = this.makeResizeImg(image);
        // リサイズ済みのBase64をblobに変換
        const resizedBlob = this.base64ToBlob(resizedBase64);
        // urlを生成してプレビュー表示できるようにする
        const resizedImg = window.URL.createObjectURL(resizedBlob);
        this.resizedImg = resizedImg;
      };
      image.src = file;
    },
    makeResizeImg(image) {
      const canvas = this.$refs.canvas;
      const ctx = canvas.getContext('2d'); // 2Dコンテキスト
      // 縦横で長い方の最大値を1000とする
      const MAX_SIZE = 1000;

      // MAX_SIZEよりも小さかったらそのまま
      if (image.width < MAX_SIZE && image.height < MAX_SIZE) {
        [canvas.width, canvas.height] = [image.width, image.height];
        ctx.drawImage(image, 0, 0);
        return canvas.toDataURL('image/jpeg');
      }

      let dstWidth;
      let dstHeight;
      // 縦横比の計算
      if (image.width > image.height) {
        dstWidth = MAX_SIZE;
        dstHeight = (image.height * MAX_SIZE) / image.width;
      } else {
        dstHeight = MAX_SIZE;
        dstWidth = (image.width * MAX_SIZE) / image.height;
      }
      canvas.width = dstWidth;
      canvas.height = dstHeight;
      // リサイズ
      ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, dstWidth, dstHeight);

      // data_url形式に変換したものを返す
      return canvas.toDataURL('image/jpeg');
    },
    clearUploadImg() {
      this.resizedImg = null;
      if (this.$refs.fileInput && this.$refs.fileInput.value !== undefined) {
        this.$refs.fileInput.value = '';
      }
    },
    base64ToBlob(base64) {
      const bin = atob(base64.replace(/^.*,/, ''));
      const buffer = new Uint8Array(bin.length);
      for (let i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
      }
      return new Blob([buffer.buffer], {
        type: 'image/png'
      });
    }
  }
};
</script>

css部分

style.scss
.resize-img {
  width: 300px;
  height: 300px;
  margin: 0 auto;
  margin-top: 20px;

  &__post {
    border: 1px solid rgba(#000, 0.16);
    line-height: 30rem;

    &__label {
      display: inline-block;
      width: 100%;
      color: rgba(0, 0, 0, 0.4);
      text-align: center;

      & > input {
        display: none;
      }
    }
  }

  &__preview {
    width: 300px;
    height: 300px;

    &__circle {
      position: absolute;
      right: 37px;
      width: 27px;
      height: 27px;
      margin: 5px;
      padding: 2px 9px;
      border-radius: 50%;
      background-color: rgba(0, 0, 0, 0.3);

      &__close-icon {
        color: #fff;
      }
    }

    &__canvas {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }
}

試しにMAX_SIZEを変更して検証

元画像のサイズ 960 × 641
スクリーンショット 2019-05-22 17.36.44.png

MAX_SIZE = 1000 の場合

スクリーンショット 2019-05-22 18.52.17.png

画像はMAX_SIZEよりも小さいので原寸表示されます

MAX_SIZE = 500 の場合

スクリーンショット 2019-05-22 18.52.45.png

画像はMAX_SIZEよりも大きいので500pxにリサイズされます。いい感じ!

参考

弊社フロントエンドエンジニアの記事!まさしくな記事で参考にさせていただきました
* Vue.jsでファイルアップロードの実装--ついでにプレビュー機能も実装した話-- - Qiita

実際にリサイズできるサービスのリンクと中のコードが書いてあって参考になりました
* HTMLとJavaScriptで画像をリサイズ | blog.PanicBlanket.com

Nuxtで書かれていて参考になりました
* Nuxt.js(vue.js)で画像を縮小してFirebaseStorageにアップロードする - Qiita

サーバーへPOSTする際のファイル形式の参考になりました
(base64とかblobとか分からなかったので・・)
* Canvasで描画した画像を送信してサーバに保存する - Qiita

canvasのdrawImageがよく分からなかったので、参考になりました
* drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)-Canvasリファレンス

79
84
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
79
84