属性名 | 属性说明 | 默认值 |
value | v-model 绑定的上传图片列表 | |
compressSwitch | 是否开启图片压缩 | false |
quality | 图片压缩质量【0-1】 | 0.1 |
threshold | 图片压缩阈值(图片大于等于该阈值才进行压缩) | 500KB |
maxCount | 图片上传数量限制 | 1 |
<template>
<div class="comp-ship">
<compress-uploader v-model="fileList" :compressSwitch="true" :quality="0.5" :maxCount="3" />
</div>
</template>
<script>
import compressUploader from './components/compress-uploader'
export default {
name: 'CompShip',
data () {
return {
fileList: [],
}
},
components: {
compressUploader
}
}
</script>
<style lang="scss" scoped>
</style>
<template>
<van-uploader :fileList="fileList" :after-read="afterRead"
multiple :max-count="maxCount" v-bind="$attrs" v-on="$listeners" />
</template>
<script>
export default {
name: 'compress-uploader',
props: {
value: Array,
quality: {
type: Number,
default: 0.1
},
compressSwitch: {
type: Boolean,
default: false
},
threshold: {
type: Number,
default: 500
},
maxCount: {
type: Number,
default: 1
}
},
computed: {
fileList: {
get () {
return this.value
},
set (n) {
this.$emit('input', n)
}
}
},
methods: {
afterRead (file, detail) {
file.status = 'uploading'
file.message = '上传中...'
this.imgPreview(file, detail.index)
return true
},
// 处理图片
async imgPreview (myFile, index) {
const file = myFile.file
if (!file || !window.FileReader) { // 看支持不支持FileReader
return
}
const size = file.size / 1024
console.log(`图片大小 ===> ${size}kb`)
console.log('图片压缩:', this.compressSwitch ? '开' : '关')
console.log('图片压缩阈值:', this.threshold + 'kb')
console.log('图片压缩降帧值:', this.quality)
if (/^image/.test(file.type) && size >= this.threshold && this.compressSwitch) {
const img = new Image()
img.src = await this.getBase64(file) // 将图片将转成base64格式
img.onload = () => {
const data = this.compress(img, file.name, file.type)
console.log(`压缩后 ===> ${data.fileData.size / 1024}kb`)
this.fileList[index] = {
content: data.base64Data,
file: data.fileData
}
myFile.status = 'done'
}
} else {
myFile.status = 'done'
}
},
// 压缩图片
compress (img, name, type) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// 瓦片canvas
const tCanvas = document.createElement('canvas')
const tctx = tCanvas.getContext('2d')
let width = img.width
let height = img.height
// 如果图片大于四百万像素,计算压缩比并将大小压至400万以下
let ratio
if ((ratio = (width * height) / 4000000) > 1) {
ratio = Math.sqrt(ratio)
width /= ratio
height /= ratio
} else {
ratio = 1
}
canvas.width = width
canvas.height = height
// 铺底色
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// 如果图片像素大于100万则使用瓦片绘制
let count
if ((count = (width * height) / 1000000) > 1) {
count = ~~(Math.sqrt(count) + 1) // 计算要分成多少块瓦片
// 计算每块瓦片的宽和高
const nw = ~~(width / count)
const nh = ~~(height / count)
tCanvas.width = nw
tCanvas.height = nh
for (let i = 0; i < count; i++) {
for (let j = 0; j < count; j++) {
tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh)
ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)
}
}
} else {
ctx.drawImage(img, 0, 0, width, height)
}
// 进行压缩
const ndata = canvas.toDataURL('image/jpeg', this.quality)
tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0
return { base64Data: ndata, fileData: this.dataURLtoFile(ndata, name, type) }
},
// 获取图片base64格式
getBase64 (data) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.onload = (e) => {
resolve(e.target.result)
}
fileReader.readAsDataURL(data)
fileReader.onerror = () => {
reject(new Error('文件流异常'))
}
})
},
// 将base64转换为文件
dataURLtoFile (dataurl, name, type) {
name = name || '图片'
type = type || 'jpg'
const arr = dataurl.split(',')
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], name, {
type: type
})
}
}
}
</script>