最终效果
原理详解
1.图片压缩参数
压缩图片的大小主要通过两种方式:
- 改变图片的尺寸–缩小宽度和高度
- 降低图片的清晰度
此demo中可以输入相关参数,配置方法如下:
- 未设置宽度和高度时,压缩时将保留图片的宽度和高度
- 仅设置宽度或仅设置高度时,压缩时将保留图片的宽高比,自动计算出对应的高度或宽度
- 图片清晰度最小值为0.01,此时图片清晰度损失最大,可能会很模糊
- 图片清晰度最大值为1,压缩时将保留图片的清晰度
- 压缩后,若图片大小变大,则可以指定更小的宽度或高度,或调小清晰度
2.文件上传组件 el-upload
- 必须要配文件上传接口action,即便此demo中为""
- 通常文件上传都需要专门配请求头 headers
- 文件上传前的文件类型校验、文件大小限制、以及此处的图片压缩等,都在before-upload中进行,因图片压缩需要耗费时间,此处需使用异步回调,即 return new Promise,无需异步回调的情况,return true 即可。
- before-upload的参数file为文件对象,内含size文件大小,name文件名称,type文件类型等信息,通过 URL.createObjectURL(file) 可转换为图片的src,不过必须要用图片对象才能接收,即:
// 获取压缩前图片的信息
this.originalImg = new Image();
this.originalImg.src = URL.createObjectURL(file);
this.originalImg.size = (file.size / 1024 / 1024).toFixed(2);
- 图片上传成功后,会触发 on-success回调函数,以便获取上传接口返回的信息。(本demo没有上传接口,不会触发on-success回调函数)
3.图片压缩原理
本案例中使用的canvas对图片进行压缩,详见 compressUpload 函数:
- 创建了一个2d的canvas节点对象
- 根据原图片大小和图片压缩配置确定压缩后图片的宽和高
- 【fillRect 】在canvas画布上绘制填充的矩形
- 【drawImage】将原图片按压缩后的尺寸大小绘制在canvas画布中
- 【toDataURL】根据配置的清晰度,将图片转化为base64格式
- 通过【dataURItoBlob】函数,将base64格式的图片转换为Blob对象,以便传给后端。
4.图片信息的获取
原图片信息的获取(在before-upload中获取)
// 获取压缩前图片的信息
this.originalImg = new Image();
this.originalImg.src = URL.createObjectURL(file);
this.originalImg.size = (file.size / 1024 / 1024).toFixed(2);
压缩后图片信息的获取(在compressUpload中通过图片的base64格式获取)
// 获取压缩后图片的信息
this.compressedImg = new Image();
this.compressedImg.src = compressData;
this.compressedImg.height = height;
this.compressedImg.width = width;
this.compressedImg.size = (blobImg.size / 1024 / 1024).toFixed(2);
5.图片的查看
使用了el-dialog的全屏模式fullscreen,以便展示超出可视区域的图片
6.图片的下载
创建一个超链接a标签,通过js触发点击事件dispatchEvent,实现图片的下载。
// 下载图片
downloadImg(imgSrc, imgName) {
let a = document.createElement("a");
let event = new MouseEvent("click");
// 自定义下载后图片的名称
a.download = imgName;
a.href = imgSrc;
a.dispatchEvent(event);
},
完整代码
<template>
<div class="container">
<h2>图片压缩</h2>
<h3>1.设置图片压缩参数</h3>
<el-collapse>
<el-collapse-item title="图片压缩参数的配置方法【必看】">
<ul>
<li>未设置宽度和高度时,压缩时将保留图片的宽度和高度</li>
<li>
仅设置宽度或仅设置高度时,压缩时将保留图片的宽高比,自动计算出对应的高度或宽度
</li>
<li>
图片清晰度最小值为0.01,此时图片清晰度损失最大,可能会很模糊
</li>
<li>图片清晰度最大值为1,压缩时将保留图片的清晰度</li>
<li>
压缩后,若图片大小变大,则可以指定更小的宽度或高度,或调小清晰度
</li>
</ul>
</el-collapse-item>
</el-collapse>
<br />
<el-form
ref="Ref_form"
:model="compressConfig"
label-width="140px"
size="mini"
style="width:400px"
>
<el-form-item
label="压缩后的图片宽度:"
prop="width"
:rules="[
{
pattern: /^[1-9]\d*$/,
message: '请输入大于0的整数!',
trigger: 'change',
},
]"
>
<el-input
clearable
placeholder="请输入大于0的整数!"
v-model.number="compressConfig.width"
>
<template slot="append">px</template>
</el-input>
</el-form-item>
<el-form-item
label="压缩后的图片高度:"
prop="height"
:rules="[
{
pattern: /^[1-9]\d*$/,
message: '请输入大于0的整数!',
trigger: 'change',
},
]"
>
<el-input
clearable
placeholder="请输入大于0的整数!"
v-model="compressConfig.height"
>
<template slot="append">px</template>
</el-input>
</el-form-item>
<el-form-item label="图片清晰度:">
<el-slider
:show-tooltip="false"
:max="1"
:min="0.01"
v-model="compressConfig.rate"
show-input
:step="0.01"
input-size="mini"
>
</el-slider>
</el-form-item>
</el-form>
<h3>2.点击上传一张图片</h3>
<el-upload
class="uploader"
:style="myStyle"
:action="uploadApiURL"
:headers="uploadHeaders"
:before-upload="beforeUpload"
:on-success="uploadSuccess"
>
<img v-if="imageUrl" :src="imageUrl" class="myImage" :style="myStyle" />
<i v-else class="el-icon-plus uploader-icon" :style="myStyle"></i>
</el-upload>
<div v-if="imageUrl" class="infoRow">
<div>
<h3>
压缩前
<el-button
@click="openImgWin('origin')"
size="mini"
type="primary"
plain
>查看图片</el-button
>
<el-button
@click="downloadImg(originalImg.src, '原图片')"
size="mini"
type="primary"
plain
>下载图片</el-button
>
</h3>
<ul>
<li>图片大小:{{ originalImg.size }} M</li>
<li>图片宽度:{{ originalImg.width }} px</li>
<li>图片高度:{{ originalImg.height }} px</li>
</ul>
</div>
<div>
<h3>
压缩后
<el-button
@click="openImgWin('compressed')"
size="mini"
type="primary"
plain
>查看图片</el-button
>
<el-button
@click="downloadImg(compressedImg.src, '压缩后的图片')"
size="mini"
type="primary"
plain
>下载图片</el-button
>
</h3>
<ul>
<li>图片大小:{{ compressedImg.size }} M</li>
<li>图片宽度:{{ compressedImg.width }} px</li>
<li>图片高度:{{ compressedImg.height }} px</li>
</ul>
</div>
</div>
<el-dialog
title="图片预览"
:visible.sync="showImg"
v-if="showImg"
fullscreen
>
<div v-if="imgMark === 'origin'">
<ul>
<li>图片大小:{{ originalImg.size }} M</li>
<li>图片宽度:{{ originalImg.width }} px</li>
<li>图片高度:{{ originalImg.height }} px</li>
</ul>
<img :src="originalImg.src" alt="原图片" />
</div>
<div v-if="imgMark === 'compressed'">
<ul>
<li>图片大小:{{ compressedImg.size }} M</li>
<li>图片宽度:{{ compressedImg.width }} px</li>
<li>图片高度:{{ compressedImg.height }} px</li>
</ul>
<img :src="compressedImg.src" alt="压缩后的图片" />
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
// 压缩配置
compressConfig: {
rate: 0.5,
},
// 图片标识
imgMark: "",
// 是否展示图片弹窗
showImg: false,
// 原图片
originalImg: {},
// 压缩后的图片
compressedImg: {},
//上传文件的接口地址
uploadApiURL: "",
//上传文件的请求头--按接口需求设置
uploadHeaders: {},
// 上传容器中显示图片
imageUrl: "",
// 上传容器的尺寸
myStyle: {
width: "100px",
height: "100px",
"line-height": "100px",
},
};
},
methods: {
// 下载图片
downloadImg(imgSrc, imgName) {
let a = document.createElement("a");
let event = new MouseEvent("click");
// 自定义下载后图片的名称
a.download = imgName;
a.href = imgSrc;
a.dispatchEvent(event);
},
// 打开弹窗——查看图片
openImgWin(mark) {
this.imgMark = mark;
this.showImg = true;
},
// 上传图片前校验压缩参数,并进行图片压缩
beforeUpload(file) {
this.$refs.Ref_form.validate((valid) => {
if (valid) {
// 获取压缩前图片的信息
this.originalImg = new Image();
this.originalImg.src = URL.createObjectURL(file);
this.originalImg.size = (file.size / 1024 / 1024).toFixed(2);
let _this = this;
return new Promise((resolve, reject) => {
let image = new Image();
image.src = URL.createObjectURL(file);
image.onload = () => {
let resultBlob = "";
// 压缩图片
resultBlob = _this.compressUpload(
image,
file,
this.compressConfig
);
resolve(resultBlob);
};
image.onerror = (err) => {
reject(err);
};
});
} else {
this.$message.error("图片压缩参数不符合规范,请修改后重试!");
return;
}
});
},
/* 图片压缩-canvas压缩 */
compressUpload(image, file, compressConfig) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let width = compressConfig.width || image.width;
let height = compressConfig.height || image.height;
// 只设置宽度时,等比计算高度
if (compressConfig.width && !compressConfig.height) {
height = (compressConfig.width / image.width) * image.height;
}
// 只设置高度时,等比计算宽度
if (compressConfig.height && !compressConfig.width) {
width = (compressConfig.height / image.height) * image.width;
}
canvas.width = width;
canvas.height = height;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0, width, height);
// 进行最小压缩0.1
let compressData = canvas.toDataURL(
file.type || "image/jpeg",
compressConfig.rate || 0.1
);
this.imageUrl = compressData;
// base64转Blob
let blobImg = this.dataURItoBlob(compressData);
// 获取压缩后图片的信息
this.compressedImg = new Image();
this.compressedImg.src = compressData;
this.compressedImg.height = height;
this.compressedImg.width = width;
this.compressedImg.size = (blobImg.size / 1024 / 1024).toFixed(2);
return blobImg;
},
/* 图片格式转换——base64转Blob对象 */
dataURItoBlob(data) {
let byteString;
if (data.split(",")[0].indexOf("base64") >= 0) {
byteString = data.split(",")[1];
} else {
byteString = unescape(data.split(",")[1]);
}
let mimeString = data
.split(",")[0]
.split(":")[1]
.split(";")[0];
let ia = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i += 1) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], { type: mimeString });
},
// 此处上传接口不可用时,此方法不会被调用
uploadSuccess(res, file) {},
},
};
</script>
<style scoped>
.uploader {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.uploader:hover {
border-color: #409eff;
}
.uploader-icon {
font-size: 28px;
color: #8c939d;
text-align: center;
}
.myImage {
display: block;
object-fit: cover;
}
.infoRow {
display: flex;
justify-content: space-between;
}
.container {
padding-left: 30px;
position: fixed;
z-index: 3000;
width: 500px;
}
</style>
测试图片