功能:本组件支持同时上传多张图(最多4张图片),并且对每张图都压缩之后上传,也支持图片回显。
基本思路:
1、获取上传 Input 中的图片对象 File
2、将图片转换成 base64 格式
3、base64 编码的图片通过 Canvas 转换压缩,这里会用到的 Canvas 的 drawImage 以及 toDataURL 这两个 Api,一个调节图片的分辨率的,一个是调节图片压缩质量并且输出的
4、转换后的图片生成对应的新图片,然后输出
注意:
图片尺寸超过大小的限制要进行按比例缩小
ios上画布绘制图片时会被旋转,要将其旋转回来
选择图片,Input 中的图片对象 File 图片要小于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 > 1024 * 1024 * 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)
// 开始读取文件,打开loading
reader.onloadstart = function () {
that.localPicArr.push('loading')
}
// 读取文件结束,无论成功还是失败,关闭loading
reader.onloadend = function () {
// 清空input
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)
// 如果上传的图片有被旋转,将其旋转回来,一般IOS手机上面才会出现这种情况
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转为blob并上传
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>
<!-- 绘制图片 隐藏 canvas作用: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],
// 过度图片对象,在mounted方法里实例化
tempImageArr: [undefined, undefined, undefined, undefined],
// 存储blob对象的数组,长度最大为4
blobArr: [],
// 存储本地图片的数组,长度最大为4
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 > 1024 * 1024 * 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)
// 开始读取文件,打开loading
reader.onloadstart = function () {
that.localPicArr.push('loading')
}
// 读取文件结束,无论成功还是失败,关闭loading
reader.onloadend = function () {
// 清空input
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)
// 如果上传的图片有被旋转,将其旋转回来,一般IOS手机上面才会出现这种情况
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转为blob并上传
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>