やりたいこと
- ユーザーが画像をアップロードできるページを作りたい
- 確認用サムネイルも表示したい
- サムネイルは「どんな画像か」がわかるだけの小さなもの
- アップロードするファイルとは別物
- フロントで圧縮してからアップすることで通信量を節約したい
- Firebase Storageの使用量も節約したい
サーバーサイドは書きたくない
手順
-
input[type="file"]
でファイルを指定する -
new FileReader()
でファイル情報を取得する -
new Image()
でimg要素を作る -
img.src
に「2.」のファイル情報を放り込んで画像を作成する -
document.createElement('canvas')
でcanvas
要素を作る - 「5.」の
canvas
に「4.」の画像をリサイズしつつ貼り付ける - あらかじめ用意しておいたサムネイル用の
canvas
要素に「4.」の画像をリサイズしつつ貼り付ける -
toDataURL('image/jpeg')
でdata_url
形式の情報を取得する - FirebaseStorageにアップする
コード
<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)
}
}
},
...
-
new Reader()
でファイル情報を取得 -
new Image()
でimg
要素を作成 - 「2.」の
img
要素に「1.」のファイル情報を与える -
image
の準備が整い次第makeImage()
とmakeThumbnail()
を呼び出す
この例では幅1280
px以上の画像を1280
pxに縮小するものです。
maxWidth
の値は用途に応じて調整してください。
画像のwidth
がmaxWidth
以上の場合は、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')
},
前述の通り、この例では1280
pxを基準としています。
用途に応じて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
を使って色んな場面で使えるリサイズ用コンポーネントとして使っています。
その方法もそのうち…