blog

vue 画像圧縮 + 複数画像のアップロード

機能:このコンポーネントは、複数の画像を同時にアップロードし、各画像を圧縮してからアップロードすることをサポートします。...

Jan 11, 2021 · 10 min. read
シェア

機能:複数のイメージを同時にアップロードしたり、各イメージを圧縮してアップロードしたり、イメージを表示し直したりすることができます。

基本的な考え方です:

1、アップロードされたInputのイメージオブジェクトファイルを取得します。

2、イメージをbase64形式に変換します。

3は、Canvasの変換圧縮を介してbase64エンコードされたイメージは、ここではCanvasのdrawImageとtoDataURL 2つのAPIを使用します、1つは、イメージの解像度を調整するために、1つは、イメージの圧縮と出力の品質を調整することです。

4、変換されたイメージは、対応する新しいイメージを生成し、出力します。

注目してください:

イメージサイズが縮小されるサイズの制限を超えています。

iosのキャンバスはイメージを描画する際に回転します。

イメージを選択し、入力ファイルのイメージオブジェクトは20M以下でなければなりません。

 /**
 * イメージを選択する
 */
 selectImgs (e) {
 var files = e.target.files
 // ファイルの選択を解除し、何もしない。
 if (files.length === 0) {
 return
 }
 // アップロードするイメージの数+すでにアップロードされているイメージの数が4より大きい場合、プロンプトが送信される。
 if (this.localPicArr.length + files.length > 4) {
 this.$store.dispatch('set_popup_datas', {
 type: 'h_tips',
 msg: this.$t('message.upload_pic_num_limit')
 })
 return
 }
 // アップロードされた写真のリストに写真でないものがあれば、プロンプトが表示される。
 const notAllImage = Object.keys(files).some((file) => files[file].type.indexOf("image") !== 0)
 if (notAllImage) {
 this.$store.dispatch('set_popup_datas', {
 type: 'h_tips',
 msg: this.$t('message.upload_pic_limit')
 })
 return
 }
 // アップロードされたイメージリストが20MBを超える場合、プロンプトが表示される。
 const isTooLarge = Object.keys(files).some((file) => files[file].size >  * 20)
 if (isTooLarge) {
 this.$store.dispatch('set_popup_datas', {
 type: 'h_tips',
 msg: this.$t('message.upload_pic_size_limit')
 })
 return
 }
 // アップロードされたファイルがすべて条件を満たしていれば、イメージの読み込みを開始する。
 Object.keys(files).some((file, index) => this.loadImages(files[file], index))
 },
 
 

ファイルの読み込みを開始し、写真の向き属性を取得します。

/** * イメージを読み込む */ loadImages (file, index) { let orientation = null //写真の向き角度属性取得、ユーザー回転コントロール //写真の向き角度属性取得、ユーザー回転制御 // Orientation写真の角度 // DateTime撮影時間 // ImageWidth写真の幅 // ImageHeight写真の高さ // イメージのデータを取得する EXIF.getData(file, function() { // イメージの全データを取得し、その値をオブジェクトとして返す。 EXIF.getAllTags(this) // イメージのデータを取得する orientation = EXIF.getTag(this, 'Orientation') }) var that = this var reader = null var picIndex = this.localPicArr.length + index reader = new window.FileReader() reader.readAsDataURL(file) // ファイルの読み込みを開始する。 reader.onloadstart = function () { that.localPicArr.push('loading') } // ファイルの読み込みを終了し、成功しても失敗しても読み込みを終了する。 reader.onloadend = function () { // 入力をクリアする that.$refs.input.value = '' } // ファイルの読み込みに失敗した reader.onerror = function () { // 読み込みに失敗したイメージを削除する。 that.localPicArr.splice(picIndex, 1) this.$store.dispatch('set_popup_datas', { type: 'h_tips', msg: this.$t('message.upload_error') }) } // 読み込み成功 reader.onload = function (e) { that.tempImageArr[picIndex] = new Image() that.tempImageArr[picIndex].onload = function () { setTimeout(() => that.compressImages(file, that.tempImageArr[picIndex].width, that.tempImageArr[picIndex].height, picIndex, orientation), 1000) } that.tempImageArr[picIndex].src= e.target.result } },

ファイルを読み込んで正常に読み込み、イメージを圧縮して描画し、アップロードします。

 /**
 * イメージを圧縮する
 */
 compressImages (file, originWidth, originHeight, index, orientation) {
 var that = this
 // 最大サイズ制限
 var maxWidth = 800, maxHeight = 800
 // 対象サイズ
 var targetWidth = originWidth, targetHeight = originHeight
 // イメージサイズがサイズ制限を超える
 if (originWidth > maxWidth || originHeight > maxHeight) {
 if (originWidth / originHeight > maxWidth / maxHeight) {
 // 横幅でサイズを制限する
 targetWidth = maxWidth
 targetHeight = Math.round(maxWidth * (originHeight / originWidth))
 } else {
 targetHeight = maxHeight
 targetWidth = Math.round(maxHeight * (originWidth / originHeight))
 }
 }
 //  
 this.cavasArr[index] = document.getElementById(`cavas${index}`)
 this.contextArr[index] = this.cavasArr[index].getContext('2d')
 // canvasイメージを拡大縮小する
 this.cavasArr[index].width = targetWidth
 this.cavasArr[index].height = targetHeight
 // キャンバスをクリアする
 this.contextArr[index].clearRect(0, 0, targetWidth, targetHeight)
 this.contextArr[index].drawImage(this.tempImageArr[index], 0, 0, targetWidth, targetHeight)
 // アップロードされたイメージが回転していたら、回転させて戻す。
 if (orientation !== undefined && orientation != 1) {
 switch (orientation) {
 case 6:
 this.contextArr[index].rotate(Math.PI / 2)
 this.contextArr[index].drawImage(this.cavasArr[index], 0, -targetHeight, targetWidth, targetHeight)
 break
 case 3:
 this.contextArr[index].rotate(Math.PI)
 this.contextArr[index].drawImage(this.cavasArr[index], -targetWidth, -targetHeight, targetWidth, targetHeight)
 case 8:
 this.contextArr[index].rotate(3 * Math.PI / 2)
 this.contextArr[index].drawImage(this.cavasArr[index], -targetWidth, 0, targetWidth, targetHeight)
 break
 }
 }
 // canvasブロブに変換してアップロードする
 this.cavasArr[index].toBlob(function (blob) {
 // blobオブジェクトを配列に格納する。
 that.blobArr.push(blob)
 // 圧縮されたイメージを使用して、キャッシュが大きくなりすぎないようにする。
 that.localPicArr.splice(index, 1, that.cavasArr[index].toDataURL())
 const params = {
 blobArr: that.blobArr,
 localPics: that.localPicArr
 }
 // 親コンポーネントにデータを渡す
 that.$emit('setPic', params)
 }, file.type || 'image/png')
 }

コンポーネントの全コード:

<template>
 <div class="cmpress-container">
 <label>
 <input ref="input" id="file" multiple class="upload-input" type="file" name="image" accept="image/*" @change="selectImgs($event)">
 <img :class="{ oc: usingType === 'oc', bp: usingType === 'bp' }" src="../../assets/img/original_collection/register/upload_icon.png">
 </label>
 <!-- イメージを表示する>
 <div
 v-for="(item, index) in localPicArr"
 :key="index"
 :class="{ oc: usingType === 'oc', bp: usingType === 'bp' }"
 class="pic-view"
 >
 <div v-if="item === 'loading'" class="loading"><div class="xz"></div></div>
 <div v-else class="img-wrapper" :class="{ oc: usingType === 'oc', bp: usingType === 'bp' }">
 <img :src="item.img_url ? item.img_url : item" class="pic-item">
 <img @click="delPic(index)" src="../../assets/img/original_collection/register/del_icon.png" class="del-icon">
 </div>
 </div>
 <!-- DrawImage キャンバスを隠す 関数:drawImage イメージを描画したら、toDataURLを使ってbase64エンコーディングに変換し、さらにblobに変換してバックエンドに渡す。 formDataを使ってリクエスト--。>
 <canvas
 v-for="(item, index) in cavasArr"
 :key="index + 100"
 :id="'cavas' + index"
 class="cp-cavas"
 ></canvas>
 </div>
</template>
<script>
import EXIF from 'exif-js'
export default {
 props: {
 usingType: {
 type: String,
 default: 'oc'
 },
 imgDatas: {
 type: Array,
 default: []
 }
 },
 data () {
 return {
 // canvas 
 cavasArr: [undefined, undefined, undefined, undefined],
 // canvas 
 contextArr: [undefined, undefined, undefined, undefined],
 // 余分なイメージオブジェクトは、マウントされたメソッドでインスタンス化される。
 tempImageArr: [undefined, undefined, undefined, undefined],
 // 最大長4のblobオブジェクトの配列を格納する。
 blobArr: [],
 // ローカルイメージの配列を保存する。
 localPicArr: []
 }
 },
 methods: {
 /**
 * データが表示されたらイメージデータを設定する。
 */
 setLocalImages () {
 this.localPicArr = this.imgDatas.slice(0)
 this.blobArr = this.imgDatas.slice(0)
 },
 /**
 * イメージを削除する
 */
 delPic (index) {
 this.localPicArr.splice(index, 1)
 this.blobArr.splice(index, 1)
 this.tempImageArr.splice(index, 1, undefined)
 const params = {
 blobArr: this.blobArr,
 localPics: this.localPicArr
 }
 // 親コンポーネントにデータを渡す
 this.$emit('setPic', params)
 },
 /**
 * イメージを選択する
 */
 selectImgs (e) {
 var files = e.target.files
 // ファイルの選択を解除し、何もしない。
 if (files.length === 0) {
 return
 }
 // アップロードするイメージの数+すでにアップロードされているイメージの数が4より大きい場合、プロンプトが送信される。
 if (this.localPicArr.length + files.length > 4) {
 this.$store.dispatch('set_popup_datas', {
 type: 'h_tips',
 msg: this.$t('message.upload_pic_num_limit')
 })
 return
 }
 // アップロードされた写真のリストに写真でないものがあれば、プロンプトが表示される。
 const notAllImage = Object.keys(files).some((file) => files[file].type.indexOf("image") !== 0)
 if (notAllImage) {
 this.$store.dispatch('set_popup_datas', {
 type: 'h_tips',
 msg: this.$t('message.upload_pic_limit')
 })
 return
 }
 // アップロードされたイメージリストが20MBを超える場合、プロンプトが表示される。
 const isTooLarge = Object.keys(files).some((file) => files[file].size >  * 20)
 if (isTooLarge) {
 this.$store.dispatch('set_popup_datas', {
 type: 'h_tips',
 msg: this.$t('message.upload_pic_size_limit')
 })
 return
 }
 // アップロードされたファイルがすべて条件を満たしていれば、イメージの読み込みを開始する。
 Object.keys(files).some((file, index) => this.loadImages(files[file], index))
 },
 /**
 * イメージを読み込む
 */
 loadImages (file, index) {
 let orientation = null
 //写真の向き角度属性取得、ユーザー回転制御
 EXIF.getData(file, function() {
 EXIF.getAllTags(this)
 orientation = EXIF.getTag(this, 'Orientation')
 })
 var that = this
 var reader = null
 var picIndex = this.localPicArr.length + index
 reader = new window.FileReader()
 reader.readAsDataURL(file)
 // ファイルの読み込みを開始する。
 reader.onloadstart = function () {
 that.localPicArr.push('loading')
 }
 // ファイルの読み込みを終了し、成功しても失敗しても読み込みを終了する。
 reader.onloadend = function () {
 // 入力をクリアする
 that.$refs.input.value = ''
 }
 // ファイルの読み込みに失敗した
 reader.onerror = function () {
 // 読み込みに失敗したイメージを削除する。
 that.localPicArr.splice(picIndex, 1)
 this.$store.dispatch('set_popup_datas', {
 type: 'h_tips',
 msg: this.$t('message.upload_error')
 })
 }
 // 読み込み成功
 reader.onload = function (e) {
 that.tempImageArr[picIndex] = new Image()
 that.tempImageArr[picIndex].onload = function () {
 setTimeout(() => that.compressImages(file, that.tempImageArr[picIndex].width, that.tempImageArr[picIndex].height, picIndex, orientation), 1000)
 }
 that.tempImageArr[picIndex].src= e.target.result
 }
 },
 /**
 * イメージを圧縮する
 */
 compressImages (file, originWidth, originHeight, index, orientation) {
 var that = this
 // 最大サイズ制限
 var maxWidth = 800, maxHeight = 800
 // 対象サイズ
 var targetWidth = originWidth, targetHeight = originHeight
 // イメージサイズがサイズ制限を超える
 if (originWidth > maxWidth || originHeight > maxHeight) {
 if (originWidth / originHeight > maxWidth / maxHeight) {
 // 横幅でサイズを制限する
 targetWidth = maxWidth
 targetHeight = Math.round(maxWidth * (originHeight / originWidth))
 } else {
 targetHeight = maxHeight
 targetWidth = Math.round(maxHeight * (originWidth / originHeight))
 }
 }
 //  
 this.cavasArr[index] = document.getElementById(`cavas${index}`)
 this.contextArr[index] = this.cavasArr[index].getContext('2d')
 // canvasイメージを拡大縮小する
 this.cavasArr[index].width = targetWidth
 this.cavasArr[index].height = targetHeight
 // キャンバスをクリアする
 this.contextArr[index].clearRect(0, 0, targetWidth, targetHeight)
 this.contextArr[index].drawImage(this.tempImageArr[index], 0, 0, targetWidth, targetHeight)
 // アップロードされたイメージが回転していたら、回転させて戻す。
 if (orientation !== undefined && orientation != 1) {
 switch (orientation) {
 case 6:
 this.contextArr[index].rotate(Math.PI / 2)
 this.contextArr[index].drawImage(this.cavasArr[index], 0, -targetHeight, targetWidth, targetHeight)
 break
 case 3:
 this.contextArr[index].rotate(Math.PI)
 this.contextArr[index].drawImage(this.cavasArr[index], -targetWidth, -targetHeight, targetWidth, targetHeight)
 case 8:
 this.contextArr[index].rotate(3 * Math.PI / 2)
 this.contextArr[index].drawImage(this.cavasArr[index], -targetWidth, 0, targetWidth, targetHeight)
 break
 }
 }
 // canvasブロブに変換してアップロードする
 this.cavasArr[index].toBlob(function (blob) {
 // blobオブジェクトを配列に格納する。
 that.blobArr.push(blob)
 // 圧縮されたイメージを使用して、キャッシュが大きくなりすぎないようにする。
 that.localPicArr.splice(index, 1, that.cavasArr[index].toDataURL())
 const params = {
 blobArr: that.blobArr,
 localPics: that.localPicArr
 }
 // 親コンポーネントにデータを渡す
 that.$emit('setPic', params)
 }, file.type || 'image/png')
 }
 }
}
</script>
<style lang="less" scoped>
@keyframes loading{
 0%{
 transform: rotate(0deg);
 }
 100%{
 transform: rotate(360deg);
 }
}
.cp-cavas {
 display: none;
}
.cmpress-container {
 width: 100%;
 height: 100%;
 font-size: .16rem;
 margin-top: .3rem;
 display: flex;
 flex-direction: row;
 justify-content: flex-start;
 .upload-input {
 display: none;
 }
 .oc {
 width: 1rem;
 height: 1rem;
 }
 .bp {
 width: 1.12rem;
 height: 1.12rem;
 }
 .pic-view {
 position: relative;
 &.oc {
 width: 1rem;
 height: 1rem;
 margin-left: .1rem;
 .del-icon {
 width: .22rem;
 height: .22rem;
 }
 }
 &.bp {
 width: 1.12rem;
 height: 1.12rem;
 margin-left: .2rem;
 .del-icon {
 width: .26rem;
 height: .26rem;
 }
 }
 .loading {
 width: 100%;
 height: 100%;
 border-radius: .1rem;
 background: rgba(0,0,0,.25);
 display: flex;
 justify-content: center;
 align-items: center;
 .xz {
 width: .6rem;
 height: .6rem;
 border: .02rem solid;
 border: .02rem solid;
 border-radius: 50%;
 border-color: #fff #fff transparent;
 animation: loading 1s linear infinite;
 }
 }
 .img-wrapper {
 overflow: hidden;
 display: flex;
 align-items: center;
 border-radius: .1rem;
 &.oc {
 width: 1rem;
 height: 1rem;
 }
 &.bp {
 width: 1.12rem;
 height: 1.12rem;
 }
 .pic-item {
 border-radius: .1rem;
 width: 100%;
 height: auto;
 position: relative;
 }
 }
 
 .del-icon {
 position: absolute;
 top: -.1rem;
 right: -.1rem;
 }
 }
 
}
</style>
Read next

JavaScriptの概要

は高レベルのインタプリタ型プログラミング言語です。プロトタイプベースの関数ファースト言語で、オブジェクト指向プログラミング、命令型プログラミング、関数型プログラミングをサポートするマルチパラダイム言語です。テキスト、配列、日付、正規表現などを操作する構文を提供します。ネットワーク、ストレージ、グラフィックなどのI/Oはサポートしていません。

Jan 11, 2021 · 2 min read