Canvas应用:图片压缩算法实现
前端图片压缩应用场景
- 前端页面限制用户只可以上传5MB大小的图片
- 前端在接受到用户上传的图片之后,服务器只允许上传1MB大小的图片,此时需要前端将图片先进行压缩,压缩之后再调用图片上传接口将图片上传
- 创建一个input元素,并且设置HTML5新增的type为file
- 监听input元素的change事件,并通过e.target.files拿到用户上传的文件列表数组
- 获取用户上传的文件对象,校验其是否为空
- 校验文件的type类型是否为图片类型
- 校验文件的size大小是否超出5MB
- 如果校验通过,那么才开始进行图片压缩
<input type="file" id="upload"></input>
<script type="text/javascript">
const upload = document.getElementById('#upload');
const acceptType = ['image/png','image/jpeg','image/jpg'];
const maxSize = 3 * 1024 *1024;
upload.addEventListener('change',function(e){
let file = e.target.files[0];
if(!file)return;
let {type:fileType,size:fileSize} = file;
if(!acceptType.includes(fileType)){
alert(`不支持${fileType}类型文件`);
upload.value = '';
return;
}
if(fileSize > maxSize){
alert(`文件大小超出3MB!`);
upload.value = '';
return;
}
convertImageToBase64(file,function(base64Image){
compress(base64Image,uploadToServer);
});
})
</script>
第二步:图片压缩第一步先将用户上传图片转化为base64格式字符串
- 创建一个浏览器内置的FileReader对象的实例reader
- 调用reader.readAsDataURL()方法,参数接收一个blob对象或者file对象
- 调用该方法之后会开始读取传入的文件file,读取完成后会触发load完成事件
- 在load事件中,通过e.target.result或者reader.result获取file对应的base64格式字符串
- 执行传入的callback函数,此时开始执行compress压缩函数
- 将reader指向null,防止内存泄漏
function convertImageToBase64(file,callback){
const reader = new FileReader();
reader.addEventListener('load',function(e){
let base64Image = e.target.result;
callback && callback(base64Image);
reader = null;
})
reader.readAsDataURL(file);
}
第三步:执行compress方法,开始压缩图片
- 创建一个image对象,为了方便获取用户上传图片的原始宽高
- 设置最大宽度和高度
- 将image的src属性设置为base64Image,并监听image的load事件
- 当load事件触发后,在事件回调函数中进行如下逻辑:
- 设置压缩比ratio
- 设置是否需要压缩的变量needCompress
- 校验原始宽度超出宽度,计算压缩比,如果超出,此时最大高度需要等比例变化
- 校验原始高度超出高度,计算压缩比,如果超出,此时最大宽度需要等比例变化
- 如果原始宽高都没有超出,那么需要将最大宽高的值修改为原始宽高
- 动态创建一个canvas元素
- 设置canvas元素的宽高属性分别为最大宽度和高度后插入到页面中
- 设置canvas的visibility属性为hidden或者visibile,看具体项目需求
- 获取ctx 2d上下文对象
- 清空上一次绘图的画布ctx.clearRect(0,0,最大宽度,最大高度)
- 绘制图片ctx.drawImage(image,0,0,最大宽度,最大高度)绘制出图片,此时已经将图片的尺寸进行了压缩
- 输出图片canvas.toDataURL('image/jpeg',0.8);前者是输出格式,后者是输出质量。选jpeg本来就会对图片进行压缩,输出质量也会进行压缩。
- 拿到上一步输出的base64格式的图片地址,将其作为参数传递给callback,compress函数中的callb18. 通过对比image.src.length可以计算压缩前后的压缩比
function compress(base64Image,callback){
const image = new Image();
let maxW = 1024,
maxH = 1024;
image.addEventListener('load',function(e){
const needCompress = false,
ratio = null;
if(image.naturalWidth > maxW){
needCompress = true;
ratio = naturalWidth / maxW;
maxH = naturalHeight/ratio;
}
if(image.naturalHeight > maxH){
needCompress = true;
ratio = naturalHeight / maxH;
maxW = naturalWidth/ratio;
}
if(!needCompress){
maxW = naturalWidth;
maxH = naturalHeight;
}
let canvas = document.createElement('canvas');
canvas.width = maxW;
canvas.height = maxH;
document.body.appendChild(canvas);
canvas.style.visibility = 'hidden';
let ctx = canvas.getContext('2d');
ctx.clearRect(0,0,maxW,maxH);
ctx.drawImage(image,0,0,maxW,maxH);image是要生成的image对象
let compressedBase64Image = canvas.toDataURL('image/jpeg',0.8);
canvas.remove();
callback && callback(compressedBase64Image);
const _ratio = base64Image.length / compressedBase64Image.length;
console.log('图片压缩比为:',_ratio);
})
image.src = base64Image;
}
第四步:将图片上传至服务器
- 接收传递的压缩后图片的base64字符串compressImage
- 将其上传至服务器
function uploadToServer(compressedBase64Image){
console.log('上传的图片为:' + compressedBase64Image);
axios.post({
url:'192.168.01.02/api/upload',
data:{
},
headers:{
'Content-Type':'multipart/form-data'
}
})
}
合并后的代码如下:
<input type="file" id="upload"/>
function convertImageToBase64(file,callback){
// 实例化FileReader对象,主要用于读取文件内容
let reader = new FileReader();
// 监听文件加载完成的事件
reader.addEventListener('load',function(e){
// 直接用reader实例的result属性拿到base64格式
// console.log(reader.result);
// e.target.result获取到文件的base64格式
// console.dir(e.target.result);
let base64Image = e.target.result;
// 执行callback()
callback && callback(base64Image);
// 将reader指向null防止内存泄漏
reader = null;
})
// 调用实例的readAsDataURL方法
reader.readAsDataURL(file);
}
function uploadToServer(compressedBase64Image){
console.log('上传的图片为:' + compressedBase64Image);
axios.post({
url:'192.168.01.02/api/upload',
data:{
},
headers:{
'Content-Type':'multipart/form-data'
}
})
}
function compress(base64Image,callback){
const image = new Image();
let maxWidth = 1024;
let maxHeight = 1024;
image.addEventListener('load',function(e){
let ratio;
let needCompress = false;
if(image.naturalWidth > maxWidth){
needCompress = true;
ratio = image.naturalWidth / maxWidth;
maxHeight = image.naturalHeight / ratio;
}
if(image.naturalHeight > maxHeight){
needCompress = true;
ratio = image.naturalHeight / maxHeight;
maxWidth = image.naturalWidth / ratio;
}
// 如果不需要压缩 那么需要获取图片的实际尺寸
if(!needCompress){
maxWidth = image.naturalWidth;
maxHeight = image.naturalHeight;
}
// 创建一个画步 画布大小 就是压缩尺寸后的图片大小
const canvas = document.createElement('canvas');
canvas.width = maxWidth;
canvas.height = maxHeight;
let ctx = canvas.getContext('2d');
ctx.clearRect(0,0,maxWidth,maxHeight);
ctx.drawImage(image,0,0,maxWidth,maxHeight);
const compressImage = canvas.toDataURL('image/jpeg',0.8);
canvas.remove();
// 此时已经拿到压缩后图片的base64格式 下面要将图片上传到服务器
callback && callback(compressImage);
const _image = new Image();
_image.src = compressImage;
_image.id = 'chooseImage';
document.body.appendChild(_image);
console.log(base64Image.length/compressImage.length);压缩比
})
image.src = base64Image;
}
const upload = document.getElementById('#upload');
const acceptType = ['image/png','image/jpeg','image/jpg'];
const maxSize = 3 * 1024 *1024;
upload.addEventListener('change',function(e){
let file = e.target.files[0];
if(!file)return;
let {type:fileType,size:fileSize} = file;
if(!acceptType.includes(fileType)){
alert(`不支持${fileType}类型文件`);
upload.value = '';
return;
}
if(fileSize > maxSize){
alert(`文件大小超出3MB!`);
upload.value = '';
return;
}
convertImageToBase64(file,function(base64Image){
compress(base64Image,uploadToServer);
});
})