canvas 可以对图片进行压缩处理,原理及处理流程大致如下:
1、先将图片的 File 文件对象转成 base64 DataURL。
2、创建一个 Image 对象,src 接收文件的 base64 DataURL 后来获取图片的宽高和比例。
3、创建 canvas 画布,并依据 Image 对象的尺寸来设置画布的大小。
4、将图片绘制到 canvas 画布面。
5、使用 canvas 的 drawImage() 方法对图像进行压缩处理,并获取新的 base64 DataURL。
6、将 base64 DataURL 转换为文件对象。
7、如果服务端是接收 File 对象(文件流,二进制)来进行上传,前端可以通过 FormData.append("file", fileObject) 的方式来追加文件对象以进行上传。
将file文件对象转化为base64 DataURL
/**
* 将文件对象转化为 base64 Data URL
* @param {二进制文件流} file
* @param {回调函数,返回 base64 Data URL} callback
**/
readFileAsDataURL(file, callback) {
// 如果file未定义,返回null
if (!file) return callback(null);
// 创建读取文件对象
let fileReader = new FileReader();
fileReader.readAsDataURL(file); // 读取file文件,得到的结果为base64位
fileReader.onload = function() {
let _this = this,
imgBase64DataURL = _this.result; // fileReader 读取到的 base64 Data URL
callback && callback(imgBase64DataURL);
}
}
将base64 DataURL转换为文件对象
/**
* 将 base64 Data URL 转换为文件对象
* @param {baseURL} dataUrl
* @param {文件名称} filename
* @return {文件二进制流}
*/
unitedDataURLToFile(dataUrl, filename) {
if (!dataUrl) return null;
let arr = dataUrl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
base64Str = atob(arr[1]),
len = base64Str.length,
unit8Arr = new Uint8Array(len);
while (len--) {
unit8Arr[len] = base64Str.charCodeAt(len);
}
return new File([unit8Arr], filename, {
type: mime
});
}
压缩方法(关键)
/**
* 压缩方法
* @param {参数obj} parms
* @param {文件二进制流} parms.file ,必传
* @param {优化目标文件大小} parms.optimumSize ,不传初始赋值 0
* @param {输出图片宽度} parms.width ,不传初始赋值 0,按比缩放无需传高度
* @param {输出图片名称} parms.fileName ,不传初始赋值 image
* @param {压缩图片程度} parms.quality ,值范围0~1,不传初始赋值 0.92。
* @param {回调函数} parms.done ,压缩后的回调函数,压缩成功将返回 file 对象,不成功则返回错误信息
**/
compress(parms) {
//文件读取与图片加载等事件都是异步的,因此需要在回调函数中才能获取返回值
if (parms && parms.done) {
//如果file没定义,返回null
if (parms.file == undefined) return parms.done(null);
let _this = this;
//给参数赋初始值
parms.optimumSize = parms.hasOwnProperty("optimumSize") ? parms.optimumSize: 0;
parms.width = parms.hasOwnProperty("width") ? parms.width: 100;
parms.filename = parms.hasOwnProperty("filename") ? parms.filename: "image";
parms.quality = parms.hasOwnProperty("quality") ? parms.quality: 0.92;
// 得到文件类型
let fileType = parms.file.type;
// console.log(fileType) //image/jpeg
if (fileType.indexOf("image") == -1) {
console.log("请选择图片文件进行压缩理");
return parms.done({
code: -1,
msg: "Only_Required_Image"
});
}
// 读取 file 文件,得到的结果为 base64 字符串 Data URL
_this.readFileAsDataURL(parms.file,
function(base64DataURL) {
if (base64DataURL) {
//如果当前 fileSize 比优化目标小,直接输出
let fileSize = parms.file.size;
if (parms.optimumSize > fileSize) {
console.log("无需压缩");
//直接返回原文件信息,parms.file 是一个 JS File 对象,dataURL 是图片的 base64 DataURL
return parms.done({
code: 0,
msg: "Need_Not_Compress",
data: {
file: parms.file,
dataURL: base64DataURL
}
});
}
let image = new Image();
image.src = base64DataURL;
image.onload = function() {
let _self = this,
scale = _self.width / _self.height; // 获得长宽比例
// console.log(scale);
//创建一个canvas,并获取其上下文
let canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
//获取压缩后的图片宽度,如果width为0,默认原图宽度
canvas.width = parms.width == 0 ? _self.width: parms.width;
//获取压缩后的图片高度,如果width为0,默认原图高度
canvas.height = parms.width == 0 ? _self.height: parseInt(parms.width / scale);
//把图片绘制到canvas上面
context.drawImage(image, 0, 0, canvas.width, canvas.height);
//压缩图片,获取到新的base64Url
let newImageData = canvas.toDataURL(fileType, parms.quality);
//将base64转化成文件流
let resultFile = _this.unitedDataURLToFile(newImageData, parms.filename);
//判断如果 optimumSize 有限制且压缩后的图片大小比优化目标大小大,就弹出错误
if (parms.optimumSize > 0 && parms.optimumSize < resultFile.size) {
console.log("未优化到目标压缩大小!上传图片尺寸太大,请重新选择合适图片上传");
parms.done({
code: -1,
msg: "Image_Too_Large"
});
} else {
//返回文件信息,resultFile 是一个 JS File 对象,dataURL 是压缩图的 base64 DataURL
parms.done({
code: 0,
msg: "Complete",
data: {
file: resultFile,
dataURL: newImageData
}
});
}
}
}
});
}
}
当然,上述三个方法是写在一个 ES module 里的,完整的代码如下:
使用示例
新建一个示例页面,主要放置一个 <input type="file"> 控件,然后监听其 change 事件,在事件中调用压缩图片的方法。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="renderer" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>前端压缩图片示例</title>
<style>
body {
font-size: 16px;
font-family: 'PingFang SC','Microsoft YaHei','Helvetica Neue','Helvetica','Arial',sans-serif;
}
.form-group {
display: flex;
margin-bottom: .5rem;
}
.file-upload {
line-height: 1.25rem;
background-color: #fff;
border: 1px solid #d4d4d4;
border-radius: .125rem;
}
.quote {
text-align:center;
color: lightgray;
}
</style>
</head>
<body>
<section>
<form name="form1">
<div class="form-group">
<div><label>上传图片:</label></div>
<div>
<input type="file" name="file" id="fileUpImg" accept="image/*" class="file-upload"/>
</div>
</div>
<div class="form-group">
<div><label style="color: transparent">图片预览:</label></div>
<div>
<div class="preview"><img id="compressionPreview" alt="预览图" src="./assets/img/default.jpg" width="256" /></div>
<div class="quote"><label id="compressionTips">请选择图片上传,稍后将进行压缩</label></div>
</div>
</div>
</form>
</section>
<!--Recommended Scripts Position-->
<script type="module">
import {ImageCompressor} from './utils/image-compressor.js';
var imgCompressor = new ImageCompressor(),
fileUpImg = document.getElementById("fileUpImg"),
compressionPreview = document.getElementById("compressionPreview"),
compressionTips = document.getElementById("compressionTips");
//监听上传控件事件
fileUpImg.onchange = function(){
compressionTips.textContent = "压缩处理中,请等待完成…";
let _this = this, fileObj = _this.files[0];
//压缩图片,这里优化目标大小设为 100KB (可能优化不成功);如果优化目标大小设为0,表示只要压缩就行,不期望达到某一目标大小
imgCompressor.compress({
file: fileObj,
optimumSize: 100*1024,
width: 480,
quality: 0.95,
done: function(res){
console.log(res);
//如果压缩成功
if(res && res.code == 0){
//TODO
let data = res.data;
if(data.dataURL) {
compressionPreview.src = data.dataURL;
compressionTips.textContent = "压缩成功!";
}
//新建一个 FormData 对象
let formData = new FormData();
formData.append("file", data.file); //加入文件对象
//可使用 Ajax 将 FormData 上传到服务端
//$.ajax({
// url: "xxx",
// type: "POST",
// data: formData,
// sunccess: function(res){}
//});
} else {
compressionTips.textContent = "压缩失败,未优化达到目标";
console.log("Compress Failed. Image Too Large");
}
}
});
};
</script>
</body>
</html>
测试结果
本示例项目参考了网上的一些代码!本来想做一个压缩图片的递归算法,直到图片大小符合优化期望。后来发现,如果优化目标大小设置得比较小,图片如何进行压缩都无法满足条件时,会造成类似“死循环”而无法跳出,浪费资源。还有一种情况就是图片进行几次压缩之后,文件大小不会有多大改变了,有时还会增加。无法有效解决此类问题,遂放弃了递归压缩。
(完)