LoginSignup
26

More than 3 years have passed since last update.

Nuxt.js(vue.js)で画像を縮小してFirebaseStorageにアップロードする

Last updated at Posted at 2019-05-12

やりたいこと

  • ユーザーが画像をアップロードできるページを作りたい
  • 確認用サムネイルも表示したい
    • サムネイルは「どんな画像か」がわかるだけの小さなもの
    • アップロードするファイルとは別物
  • フロントで圧縮してからアップすることで通信量を節約したい
    • Firebase Storageの使用量も節約したい
  • サーバーサイドは書きたくない

手順

  1. input[type="file"]でファイルを指定する
  2. new FileReader()でファイル情報を取得する
  3. new Image()でimg要素を作る
  4. img.srcに「2.」のファイル情報を放り込んで画像を作成する
  5. document.createElement('canvas')canvas要素を作る
  6. 「5.」のcanvasに「4.」の画像をリサイズしつつ貼り付ける
  7. あらかじめ用意しておいたサムネイル用のcanvas要素に「4.」の画像をリサイズしつつ貼り付ける
  8. toDataURL('image/jpeg')data_url形式の情報を取得する
  9. FirebaseStorageにアップする

コード

photoResize.vue
<template>
  <div>
    <input type="file" v-on:change="resize" accept=".jpg, .png" ref="input">
    <div>
      <canvas ref="thumbnail" :width="0" :height="0">
      <button v-on:click="reset">×</button>
    </div>
    <div>
      <button v-on:click="upload">upload</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      newImage: '',
    }
  },
  methods: {
    resize(e) {
      const file = e.target.files[0]
      const image = new Image()
      const reader = new FileReader()
      const vm = this

      reader.readAsDataURL(file)
      reader.onload = (e) => {
        image.src = e.target.result
        image.onload = () => {
          vm.newImage = this.width < 1280 ? this.src : vm.makeImage(image)
          vm.makeTumbnail(image)
        }
      }
    },
    makeImage(image) {
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      const ratio = image.height / image.width
      const width = 1280
      const height = width * ratio
      canvas.width = width
      canvas.height = height
      ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height)
      return canvas.toDataURL('image/jpeg')
    },
    makeTumbnail(image) {
      const canvas = this.$refs.thumbnail
      const ctx = canvas.getContext('2d')
      const ratio = image.width / image.height
      const height = 120
      const width = height * ratio
      canvas.height = height
      canvas.width = width
      ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height)
    },
    reset() {
      const canvas = this.$refs.thumbnail
      this.newImage = ''
      canvas.height = 0
      canvas.width = 0
      this.$refs.input.value = ''
    },
    upload() {
      const photo = this.newImage
      const storage = firebase.storage()
      const ref = storage.ref().child('main.jpg')
      const vm = this
      ref.putString(photo, 'data_url').then(snapshot => {
        console.log('photo uploaded')
        vm.reset()
      })
    },
  }
}
</script>


詳細

HTML部分

<template>
  <div>
    <input type="file" v-on:change="resize" accept=".jpg, .png" ref="input">
    <div>
      <!-- サムネイル用canvas -->
      <canvas ref="thumbnail" :width="0" :height="0">

      <!-- 選択した画像をリセットするためのボタン -->
      <button v-on:click="reset">×</button>
    </div>
    <div>
      <!-- アップロードボタン -->
      <button v-on:click="upload">upload</button>
  </div>
</template>

inputではv-modelを使いたいところですがtype="file"では使えません。
v-on="change"でメソッドを呼び出します。
また、サムネイルを表示するためのcanvas要素を予め書いています。
リセットボタンはあると便利かなという程度です。

画像を作る

  data() {
    return {
      newImage: '',
    }
  },
  methods: {
    resize(e) {
      const file = e.target.files[0]
      const image = new Image()
      const reader = new FileReader()
      const vm = this
      const maxWidth = 1280

      reader.readAsDataURL(file)

      reader.onload = (e) => {
        image.src = e.target.result

        image.onload = () => {
          vm.newImage = this.width < maxWidth ? this.src : vm.makeImage(image)
          vm.makeTumbnail(image)
        }
      }
    },
...
  1. new Reader()でファイル情報を取得
  2. new Image()img要素を作成
  3. 「2.」のimg要素に「1.」のファイル情報を与える
  4. imageの準備が整い次第makeImage()makeThumbnail()を呼び出す

この例では幅1280px以上の画像を1280pxに縮小するものです。
maxWidthの値は用途に応じて調整してください。
画像のwidthmaxWidth以上の場合は、makeImage()で縮小画像を生成、maxWidth以下の場合は入力した画像をそのままnewImageに格納します。

縮小画像を作る

    makeImage(image) {
      // canvas要素を作成
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')

      // 縦横比を算出
      const ratio = image.height / image.width

      // 生成する画像の横幅
      const width = 1280

      const height = width * ratio
      canvas.width = width
      canvas.height = height

      // canvas描画作成
      ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height)

      // data_url形式に変換したものを返す
      return canvas.toDataURL('image/jpeg')
    },

前述の通り、この例では1280pxを基準としています。
用途に応じてwidthの値を調整してください。

ここまででアップロードする画像の作成は完了です。

サムネイルを作成する

    makeTumbnail(image) {
      // 予めHTMLに記述したcanvasを指定
      const canvas = this.$refs.thumbnail
      const ctx = canvas.getContext('2d')

      // 縦横比を算出
      const ratio = image.width / image.height

      // サムネイルのサイズを指定
      const height = 120
      const width = height * ratio

      // canvasの大きさを指定
      canvas.height = height
      canvas.width = width

      // サムネイルに画像を描画
      ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height)
    },

この例ではheight="120px"の小さいサムネイルを作成しています。
用途に応じて調整してください。

入力をリセット

    reset() {
      const canvas = this.$refs.thumbnail
      this.newImage = ''

      // サムネイル用canvasのサイズを0に
      canvas.height = 0
      canvas.width = 0

      // inputの入力をリセット
      this.$refs.input.value = ''
    },

画像リセット用メソッドです。
用途に応じて。

画像をアップロードする

    upload() {
      const photo = this.newImage
      const storage = firebase.storage()

      // アップロード先のフォルダ、ファイル名を指定
      const ref = storage.ref().child('main.jpg')

      const vm = this

      // ファイルをアップロード
      ref.putString(photo, 'data_url').then(snapshot => {
        console.log('photo uploaded')

        // 入力をリセット
        vm.reset()
      })
    },

firebaseに画像をアップします。
storage.ref().child('main.jpg')がアップロード先です。
storage.ref().child('hozon/shitai/basho/main.jpg')とすると保存フォルダを指定できます。

まとめ

コンポーネントとして色んなシチュエーションで使いまわしたいということもあると思います。
現場ではprop$emitを使って色んな場面で使えるリサイズ用コンポーネントとして使っています。
その方法もそのうち…

参考

JavaScript で画像をリサイズする方法
ブラウザでローカル画像をリサイズしてアップロード

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
26