一. 上传前压缩图片的好处
- 可以减少用户的
等待时间
,提升使用体验,目前手机拍摄的图片文件大小一般在几 M 左右,文件直接上传时会有卡顿现象。 - 可以减少
服务端
的存储空间
。 - 再次回去图片资源是也可以快速的加载。虽然目前阿里云的 oss 有相对应的 api 可以通过降低图片质量等方法减少体积,不过使用 canvas 可以直接减少源文件的体积。
因此我们很有必要对上传的图片进行压缩。
二. 处理流程
主要包括以下流程:
- 用户通过
van-uploader
选择图片。 - 使用
FileReader
进行图片预览。 - 将图片绘制到
canvas
画布上,使用canvas画布的能力进行图片压缩
,todataurl()
方式会把图片自动转成base64。 - 将压缩后的
Base64
(DataURL)格式的数据转换成Blob对象
进行上传,或者直接上传base64格式,这个根据自己需求。 - 将图片
上传服务端
,上传时候创建Formdata对象实例new FromData()
,把base64或Blob,append()
到Formdata里面请求服务端接口,提交图片。 - 上传成功,后台接口返回图片的URL。
三. 了解概念
在写代码之前,先来了解几个概念。当然也可以跳过这部分,直接看代码。
1)new FileReader()
我们先来看看官方文档的介绍 FileReader
FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
FileReader常用的两个方法如下:
FileReader.onload
: 处理load事件。即该钩子在读取操作完成时触发,通过该钩子函数可以完成例如读取完图片后进行预览的操作,或读取完图片后对图片内容进行二次处理等操作。
FileReader.readAsDataURL()
:读取方法,并且读取完成后,result
属性中将包含一个data:
URL 格式的 Base64 字符串以表示所读取文件的内容(比如图片)。
在图片上传中,我们可以通过readAsDataURL()
方法进行了文件的读取,并且通过result
属性拿到了图片的Base64(DataURL)
格式的数据,然后通过该数据实现了图片预览
的功能。
2)new FormData()
我们先来看看官方文档的介绍 FormData 对象的使用
FormData 对象用以将数据编译成键值对
,以便用XMLHttpRequest
来发送数据。其主要用于发送表单数据,但亦可用于发送带键数据 (keyed data),而独立于表单使用。如果表单enctype属性设为 multipart/form-data
,则会使用表单的submit()
方法来发送数据,从而,发送数据具有同样形式。
let formdata = new FormData()
console.log(formdata)
操作方法:new FormData()对象的作用以及使用方法
3)new Image()
我们先来看看官方文档的介绍 Image()
Image()
函数将会创建一个新的HTMLImageElement
实例。它的功能等价于 document.createElement('img')
。
Image对象是JS中的宿主(或内置)对象,他代表嵌入的图像。当我们创建一个Image对象时,就相当于给浏览器缓存了一张图片,Image对象也常用来做预加载图片(也就是将图片预先加载到浏览器中,当浏览图片的时候就能享受到极快的加载速度)。在HTML页面中,标签每出现一次,也就创建了一个Image对象。
建立图像对象:`let 图像对象名称=new Image([宽度],[高度])`
图像对象的属性: `border complete height hspace lowsrc name src vspace width`
图像对象的事件:`onabort onerror onkeydown onkeypress onkeyup onload`
var img = new Image(); //创建一个Image对象 ,会渲染为img标签
img.src = "17.png"; //相当于给浏览器缓存了一张图片
img.onload = function(){alert("img is loaded")}; //onload 事件在图片加载完成后立即执行。
img.onerror = function(){alert("error!")}; // 当图片加载出现错误,会触发,经常在该事件中将图片导向报错图片,以免页面上出现红色的叉叉
img.border="3px solid #ccc";
4)canvas的toDataURL()方法
我们先来看看官方文档的介绍HTMLCanvasElement.toDataURL()
HTMLCanvasElement.toDataURL()
方法返回一个包含图片展示的 data URI
。可以使用 type
参数其类型,默认为 PNG
格式。图片的分辨率为 96dpi。
语法:
canvas.toDataURL(type, encoderOptions);
参数:
type:图片格式,默认为 image/png,可以是其他image/jpeg等
encoderOptions:0到1之间的取值,主要用来选定图片的质量,只要导出为jpg和webp格式的时候此参数才有效果。默认值是0.92,超出范围也会选择默认值。
返回值:
返回值是一个数据url,是base64组成的图片的源数据、可以直接赋值给图片的src属性。
5)图片的展示方式有三种:分别为file(文件流)、blob(本地流)、base64(二进制流)
四. 代码实现
1)通过van-uploader
来获取图片
<van-field name="business_license" :rules="[{ required: true, message: '请上传图片' }]">
<template #input>
<van-uploader
:max-size="3000 * 1024"
@oversize="onOversize"
:after-read="afterReadLicense"
:before-delete="deleteLicense"
v-model="form.licenseArray"
:max-count="1"
/>
</template>
</van-field>
//获取上传图片大小,进行压缩处理
compressImg (file) {
let quality = 1
if (file.file.size / (1024 * 1024) > 1) {
quality = 1 / Math.ceil(file.file.size / (1024 * 1024)) // 默认到1m以下
}
quality = quality / 2 // 默认质量减半(超过1m的图片,默认质量为500k)
var that = this
return new Promise(function (resolve, reject) {
if (file == []) {
reject(error)
} else {
if (that.isImageAutomaticRotation) {
that.imgHandle(file.file, quality).then(res => {
resolve(res)
})
} else {
lrz(file.file, { quality: quality }).then(res => {
resolve(res.base64)
})
}
}
})
},
// 这是图片上传压缩的核心所在,我们先使用CanvasRenderingContext2D.drawImage()方法将上传的图片文件在画布上绘制出来;
// 再使用Canvas.toDataURL()将画布上的图片信息转换成base64(DataURL)格式的数据。
imgHandle (file, quality) {
return new Promise(function (resolve, reject) {
let fileType = file.type
let imgResult = ''
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function () {
let img = new Image() //先创建图片对象
img.src = this.result
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
img.onload = function () { //图片加载完后
// 做适配
let width = 500
if (img.width < 500) {
width = img.width
}
canvas.width = width
canvas.height = (img.height / img.width) * width
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) //绘制图像;把大图片画在一张小画布上,压缩就这么实现了
// 返回base64
//quality表示导出的图片质量,只要导出为jpg和webp格式的时候此参数才有效果,默认值是0.92
imgResult = canvas.toDataURL(fileType, quality)
resolve(imgResult)
// 这时可能后端要求我们传文件格式图片 base64转Blob
// const blobImg = that.converrVase64UrlToBlob(imgResult , fileType)
// resolve(blobImg)
}
}
reader.onerror = function (error) {
reject(error)
}
})
},
// base64转Blob
converrVase64UrlToBlob (base64, mimeType) {
// mimeType 图片类型,例如 mimeType='image/png'
const bytes = window.atob(base64.split(',')[1]) // atob方法用于解码base64
// 创建一个长度为 bytes.length 的 buffer(一个二进制文件), 它会分配一个 16 字节(byte)的连续内存空间,并用 0 进行预填充。
const ab = new ArrayBuffer(bytes.length)
// Uint8Array —— 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位)。这称为 “8 位无符号整数”。
const ia = new Uint8Array(ab)
for (let i = 0; i < bytes.length; i++) {
// 更改里面的初始化内容
ia[i] = bytes.charCodeAt(i)
}
// 创建blob格式数据,并传入二进制文件和文件原本类型
return new Blob([ia], { type: mimeType })
},
// 上传前置处理
// 将图片上传到服务端
afterReadLicense (file) {
this.compressImg(file).then(res => {
let formData = new FormData()
formData.append('m_pic', res) //append方法往formData里添加数据
formData.append('token', sessionStorage.getItem('token'))
console.log(formData.get('m_pic'), 'm_pic') //只能通过get方法查看添加的m_pic
//请求接口,上传图片
indexApi
.uploadImg(formData)
.then(response => {
if (response.data.code === 200) {
this.form.business_license = response.data.data.url //赋值
} else {
this.$toast.fail(response.data.message)
}
})
.catch(error => {
console.log(error)
})
})
},
// 封装的上传图片接口
uploadImg (val) {
return formRequest({
url: 'merchant/upload_img',
method: 'post',
data: val,
headers: {
'Content-Type': 'multipart/form-data'
}
})
},
注意:
在实际开发中,我们要不要把图片转化为FormData形式上传到服务端,这就看具体的业务需要了。
我们还可以把canvas压缩
过的图片,通过axios
请求 URL,并通过FormData
附带额外的参数,上传到阿里云oss
,会返回一个url
,就是我们上传到阿里云的图片地址;然后前端拼成一个图片 url
用于from表单提交上传。
学习过程中参考了其他文章:
浅析图片上传及canvas压缩的流程(canvas图片压缩)
Canvas怎么实现上传并压缩图片
vue + elementUi + upLoadIamge组件 上传文件到阿里云oss