前言:
随着现在手机像素,拍照功能越来越好,随之而来的是本地图片越来越大,那么如何更好的将本地图片上传到后端接口呢?这是后台管理系统常见的场景和头疼的问题,这里分享下个人的方法。
实现效果:
如下图所示,从 580kb -> 压缩后 150kb
实现步骤:
1、父级引入封装文件
//页面上
<el-dialog
title="压缩图片"
:visible.sync="compressImgVisible"
width="700"
foot-hide
>
<uploadImg
/>
</el-dialog>
//js中
data() {
return {
compressImgVisible: false,
}
2、首先实现上传功能,使用el-upload,
封装uploadImg.vue
<template>
<div class="uploadImgBody">
<!--上传图片部分-->
<el-upload
class="upload-image"
ref="upload"
:action="action"
:headers="headers"
:multiple="multiple"
:data="data"
:name="name"
:show-file-list="showFileList"
:drag="drag"
:accept="accept"
:list-type="listType"
:auto-upload="autoUpload"
:disabled="is_disabled"
:before-upload="beforeUpload"
>
<!--弹框展示上传以后的图片-->
<img
class="fileImg"
v-if="mrImgUrl"
:src="mrImgUrl"
>
<div v-else>
<i class="el-icon-plus"></i>
</div>
</el-upload>
</div>
</template>
<script>
//element的上传图片,压缩图片组件
export default {
props:{
/**
* 自动上传参数
* */
autoUpload:{ // 是否需要选取完自动上传功能
type: Boolean,
default: true
},
// 默认图片,父级传过来 http开头的文件
mrImgUrl:{
type: String,
default: ''
},
action:{//上传的地址
type: String,
default: ''
},
headers: {//设置上传的请求头部
type:Object,
default: () => {
return {}
}
},
data: {//上传时额外带的参数
type:Object,
default: () => {
return {}
}
},
name:{//上传的文件字段名
type: String,
default: 'file'
},
cookieOK:{//支持发送 cookie 凭证信息
type: Boolean,
default: true
},
/**
* 公共参数
* */
showFileList:{//是否显示已上传文件列表
type: Boolean,
default: false
},
drag:{//是否启用拖拽上传
type: Boolean,
default: false
},
accept:{//接受文件类型-图片上传类型-不同的格式之间以逗号隔开
type: String,
default: '.jpg,.jpeg,.png'
},
listType:{ // 文件列表的类型 - text/picture/picture-card
type: String,
default: 'picture-card'
},
fileList:{//已上传的文件列表,
type:Array,
default: () => {
return [
{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
}
]
}
},
is_disabled:{//是否禁止,true是禁止,false不禁止
type: Boolean,
default: false
},
multiple:{//是否可以多选
type: Boolean,
default: true
},
},
watch: {},
data() {
return {
}
},
methods: {
/**
* @上传文件之前的钩子
* @params file 图片的file文件
* @return uploadFile 把文件发送给父级
* @tip 多选会调用多次该方法
*/
beforeUpload(file) {
this.$emit('uploadFile',file);
return
}
},
}
</script>
<style lang='scss' scoped>
.uploadImgBody{
height: auto;
.upload-image{
width:200px;
height: 200px;
.fileImg{
width:100%;
height: 100%;
}
}
.showImg{
width:100px;
height: 100px;
}
}
</style>
3、加入压缩功能
逻辑:
首先,把file文件转成 canvas图片,然后canvas压缩图片利用canvas.toDataURL()将canvas绘制的图像转成图片从而达到压缩图片尺寸的效果
HTMLCanvasElement.toDataURL()
具体方法:其中 dataUrl 就是拿到的canvas图片的base64地址
/**
* @压缩公共方法
* @params file
* @return 压缩后的文件,支持两种,file和 blob
*/
compressImg(file) {
const reader = new FileReader();
// readAsDataURL 方法会读取指定的 Blob 或 File 对象。读取操作完成的时候,readyState 会变成已完成DONE,并触发 loadend (en-US) 事件,
// 同时 result 属性将包含一个data:URL格式的字符串(base64编码)以表示所读取文件的内容。
reader.readAsDataURL(file);
reader.onload = () => {
const img = new Image();
img.src = reader.result;
img.onload = () => {
// 图片的宽高
const w = img.width;
const h = img.height;
const canvas = document.createElement("canvas");
// canvas对图片进行裁剪,这里设置为图片的原始尺寸
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
// canvas中,png转jpg会变黑底,所以先给canvas铺一张白底
ctx.fillStyle = "#fff";
// fillRect()方法绘制一个填充了内容的矩形,这个矩形的开始点(左上点)在
// (x, y) ,它的宽度和高度分别由width 和 height 确定,填充样式由当前的fillStyle 决定。
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制图像
ctx.drawImage(img, 0, 0, w, h);
// canvas转图片达到图片压缩效果
// 返回一个包含图片展示的 data URI base64 在指定图片格式为 image/jpeg 或 image/webp的情况下,
// 可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
const dataUrl = canvas.toDataURL("image/jpeg", 0.8);
this.dialogImageUrl = dataUrl
};
};
},
4、拿到的base64地址,不能直接给后端,要转格式,这里提供两种,一是file文件,跟压缩前的格式一样,还有一种是blob方法
// canvas生成的格式为base64,需要进行转化, base64->file
dataURLtoFile(dataurl,fileName) {
let arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], fileName, {type:mime})
},
// canvas生成的格式为base64,需要进行转化, base64->blob
dataURLtoBlob(dataurl) {
const arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
实现源码(上传+压缩):
uploadImg.vue
<template>
<div class="uploadImgBody">
<!--上传图片部分-->
<el-upload
class="upload-image"
ref="upload"
:action="action"
:headers="headers"
:multiple="multiple"
:data="data"
:name="name"
:show-file-list="showFileList"
:drag="drag"
:accept="accept"
:list-type="listType"
:auto-upload="autoUpload"
:disabled="is_disabled"
:before-upload="beforeUpload"
>
<!--弹框展示上传以后的图片-->
<img
class="fileImg"
v-if="mrImgUrl"
:src="mrImgUrl"
>
<div v-else>
<i class="el-icon-plus"></i>
</div>
</el-upload>
源图片大小:<span v-if="sourceFile.size">{{ sourceFile.size/1024 }}</span> kb
<el-button @click="compressImgFun" style="display: block;margin:10px 0;">点我压缩</el-button>
压缩图片大小:<span v-if="compressFile.size">{{compressFile.size/1024 }}</span> kb
<img
v-if="dialogImageUrl"
class="showImg"
:src="dialogImageUrl"
>
</div>
</template>
<script>
//element的上传图片,压缩图片组件
export default {
props:{
/**
* 自动上传参数
* */
autoUpload:{ // 是否需要选取完自动上传功能
type: Boolean,
default: true
},
// 默认图片,父级传过来 http开头的文件
mrImgUrl:{
type: String,
default: ''
},
action:{//上传的地址
type: String,
default: ''
},
headers: {//设置上传的请求头部
type:Object,
default: () => {
return {}
}
},
data: {//上传时额外带的参数
type:Object,
default: () => {
return {}
}
},
name:{//上传的文件字段名
type: String,
default: 'file'
},
cookieOK:{//支持发送 cookie 凭证信息
type: Boolean,
default: true
},
/**
* 公共参数
* */
showFileList:{//是否显示已上传文件列表
type: Boolean,
default: false
},
drag:{//是否启用拖拽上传
type: Boolean,
default: false
},
accept:{//接受文件类型-图片上传类型-不同的格式之间以逗号隔开
type: String,
default: '.jpg,.jpeg,.png'
},
listType:{ // 文件列表的类型 - text/picture/picture-card
type: String,
default: 'picture-card'
},
fileList:{//已上传的文件列表,
type:Array,
default: () => {
return [
{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
}
]
}
},
is_disabled:{//是否禁止,true是禁止,false不禁止
type: Boolean,
default: false
},
multiple:{//是否可以多选
type: Boolean,
default: true
},
},
watch: {},
data() {
return {
dialogImageUrl:'',//源图片
sourceFile:{}, //上传后的图片
compressFile:{} //压缩后的图片
}
},
methods: {
/**
* @上传文件之前的钩子
* @params file 图片的file文件
* @return uploadFile 把文件发送给父级
* @tip 多选会调用多次该方法
*/
beforeUpload(file) {
this.sourceFile = file
this.$emit('uploadFile',file);
return
},
compressImgFun(){
this.compressImg(this.sourceFile)
console.log('压缩前的图片文件:file');
console.log(this.sourceFile);
},
/**
* @压缩公共方法
* @params file
* @return 压缩后的文件,支持两种,file和 blob
*/
compressImg(file) {
const reader = new FileReader();
// readAsDataURL 方法会读取指定的 Blob 或 File 对象。读取操作完成的时候,readyState 会变成已完成DONE,并触发 loadend (en-US) 事件,
// 同时 result 属性将包含一个data:URL格式的字符串(base64编码)以表示所读取文件的内容。
reader.readAsDataURL(file);
reader.onload = () => {
const img = new Image();
img.src = reader.result;
img.onload = () => {
// 图片的宽高
const w = img.width;
const h = img.height;
const canvas = document.createElement("canvas");
// canvas对图片进行裁剪,这里设置为图片的原始尺寸
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
// canvas中,png转jpg会变黑底,所以先给canvas铺一张白底
ctx.fillStyle = "#fff";
// fillRect()方法绘制一个填充了内容的矩形,这个矩形的开始点(左上点)在
// (x, y) ,它的宽度和高度分别由width 和 height 确定,填充样式由当前的fillStyle 决定。
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制图像
ctx.drawImage(img, 0, 0, w, h);
// canvas转图片达到图片压缩效果
// 返回一个包含图片展示的 data URI base64 在指定图片格式为 image/jpeg 或 image/webp的情况下,
// 可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
const dataUrl = canvas.toDataURL("image/jpeg", 0.8);
this.dialogImageUrl = dataUrl
// base64格式文件转成Blob文件格式
let blobFile = this.dataURLtoBlob(dataUrl);
console.log("压缩后的图片:Blob文件----------");
console.log(blobFile);
// base64格式文件转成file文件格式
let fileName = this.sourceFile.name
let fileImg = this.dataURLtoFile(dataUrl,fileName);
console.log("压缩后的图片:file文件----------");
console.log(fileImg);
this.compressFile = fileImg
};
};
},
// canvas生成的格式为base64,需要进行转化, base64->file
dataURLtoFile(dataurl,fileName) {
let arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], fileName, {type:mime})
},
// canvas生成的格式为base64,需要进行转化, base64->blob
dataURLtoBlob(dataurl) {
const arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
},
}
</script>
<style lang='scss' scoped>
.uploadImgBody{
height: auto;
.upload-image{
width:200px;
height: 200px;
.fileImg{
width:100%;
height: 100%;
}
}
.showImg{
width:100px;
height: 100px;
}
}
</style>
更多资料:
前端压缩图片上传_泡泡大怪兽的博客-CSDN博客
前端图片压缩(几乎无损)_蓝格子.的博客-CSDN博客_前端无损压缩