最终效果
原理详解
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>
测试图片