图片压缩算法

      通过canvasAPI进行在Web端上传的时候进行图片压缩。

通过宽高压缩(第一次压缩 - Canvas宽高)
通过API压缩(第二次压缩 - HTMLCanvasDom.toDataURL())

简述步骤

  1. 通过input输入框用来坐上传,通过chang事件获得上传的文件。
  2. 对上传的文件进行一些简单的类型,大小的判断然后开始压缩。
  3. 压缩图片第一步将用户上传的图片(file)转为base64格式-new FileReader() -> ReaderAsDataUrl()异步读取 -> load事件读取完成获取base64
  4. 计算压缩图片宽高(等比例),创建Canvas画布赋值新的宽高。(第一次压缩尺寸)然后将canvasDom进行隐藏,挂载到dom节点上。
  5. getContext('2d')获得canvas对象,调用drawImage方法进行绘制。
  6. 然后通过CanvasHTMLDOM调用toDataURL方法将整个canvas画布输出成base64格式。(第二次压缩)
  7. 删除CanvasDOM,然后将压缩后的base64通过callback传入到服务端中就可以了。

Tips

  •   通过change事件进行异步完成监听
  •   new FileReader() -> reader.readAsDataURL(file) 读取上传文件 => reader.result获得base64格式的上传图片
  •   压缩对宽度和高度进行压缩 naturalWidth/naturalHeihgt   HTML5属性获得源生图片高度宽度
  •   创建Canvas标签赋值压缩后的宽高
  •   隐藏使用visibility-hidden 仅仅是样式隐藏。因为visibility属性隐藏可以获取DOM
  •   ctx.clearReact() ->   每次重新上传文件要清除Canvas画布,防止图片覆盖。
  •   ctx.drawImage()通过canvas绘制对应文件。
  •   HTMLCanvasDOM.toDataUrl()
      将canvas画布转化成base64格式同时进行压缩,得到返回值。
  •   压缩成功,使用callback传入新的Data URL进行向后台发送逻辑请求。

代码演示

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>canvas图片压缩算法</title>
</head>

<body>
<!-- 图片上传到服务端之前进行图片压缩 -->
<input type="file" id="file" />
<script>
const ACCEPT = ['image/jpg', 'image/png', 'image/jpeg']
const upload = document.getElementById("file")
const MAXSIZE = 1024 * 1024;
const MAXTIP = "1MB"

function convertImageToBase64(file, callback) {
// 创建一个FileReader对象,它允许Web应用程序异步读取存储在计算机上的文件
// 也就是file对象
let reader = new FileReader()
// 添加一个load事件,load会在加载完毕之后进行触发(也就是readAsDataURL读取完毕后触发)
reader.addEventListener("load", function (e) {
const base64Image = e.target.result // 相当于reader.result 获取文件的Base64
// 回收内存
callback && callback(base64Image) // 调用callback压缩
reader = null
})

// readAsDataURL方法读取指定的file或blob对象
reader.readAsDataURL(file)
}
// 压缩算法函数
/*
1.首先拿到了base64的图片字符串
2.创建一个image对象,获得原始图片的宽度和高度
3.对原始图片的宽度和高度进行压缩达到符合条件(第一次压缩-从尺寸压缩)
4.调用canvasAPI进行绘制新的图片
5.绘制成功之后调用canvasAPI进行绘制(canvasAPI支持压缩-二次压缩-从质量压缩)
6.得到压缩后的base64
*/
function compress(base64Image, callback) {
let maxW = 1024;
let maxH = 1024;
const image = new Image() // 创建image对象 相当于创建a标签
image.addEventListener('load', function (e) {
// image加载完成后就会触发 也就是src加载后
let radio; // 压缩比例
let needCompress = false; // 是否需要压缩
// image.naturalWidth/naturalHeight H5新属性 获取源生图片的宽高
if (image.naturalWidth > maxW) {
needCompress = true;
// 获得压缩宽高过后的大小(保证等比例缩放)
radio = image.naturalWidth / maxW
maxH = image.naturalHeight / radio
}
// 同样宽度压缩之后 还要看压缩后的高度是否满足 不满足则继续压缩宽高
if (image.naturalHeight > maxH) {
needCompress = true;
radio = image.naturalHeight / maxH
maxW = image.naturalWidth / radio
}
// 不需要压缩
if (!needCompress) {
maxW = image.naturalWidth;
maxH = image.naturalHeight;
}
// 第一次压缩完成
// 接下来使用canvas进行质量压缩
const canvas = document.createElement('canvas')
canvas.height = maxH;
canvas.width = maxW;
canvas.setAttribute("id", "_compress_")
// visibility hidden 需要创建的canvas隐藏 而不是不渲染DOM
canvas.style.visibility = 'hidden'
document.body.appendChild(canvas)

const ctx = canvas.getContext('2d')
// canvas.clearRect() 方法清空给定矩形内的指定像素。(x1,y1,width,height)
// 防止重新上传覆盖
ctx.clearRect(0, 0, maxW, maxH)
// canvas.drawImage() 方法在画布(canvas)上绘制图像、画布或视频。
// 传入 视频/图片对象 起始点x 起始点y 绘制宽 绘制高
ctx.drawImage(image, 0, 0, maxW, maxH)
// 接来下就是压缩canvas 通过API将canvas输出成base64格式

/**
* @HTMLCanvasElement.toDataURL(type, encoderOptions); 注意调用这是 Canvas的Dom对象而非ctx
* @param {String} 可以使用 type 参数其类型, type 图片格式,默认为image/png,图片的分辨率为96dpi。
* @param {Number} encoderOptions 可选 指定图片格式是image/jpeg或image/webp的情况下,可以从0到1区间内进行选择图片的质量(1原质量)。如果超出取值方位,使用默认0.92
* @returns {dataURL} 方法返回一个包含图片展示的 data URI (Base64)
*/
console.log(base64Image, 'base64Image')
/* 注意这里是Canvas DOM 节点 而非canvas对象*/
const compressImage = canvas.toDataURL('image/jpeg', 0.8) // 通常压缩是0.8-0.9
callback && callback(compressImage); // 压缩完成进行后台传输逻辑
// 压缩完的图片就已经保存在内存(compressImage)中了
// 接下来移除canvas元素 调用DOM.remove()
canvas.remove()

// 需要的上传预览的话可以单独建一个new image进行预览
const _image = new Image()
_image.src = compressImage;
document.body.appendChild(_image)
//计算压缩比 使用src(base)的长度就可以对比了
console.log(`压缩比${image.src.length/_iamge.src.length}`)


})
image.src = base64Image;
// document.body.appendChild(image) // 挂载

}
// 绑定上传图片的change事件
// mock上传后台逻辑
function uploadToServer(compressImage) {
console.log("上传后台")
}
upload.addEventListener('change', function (e) {
// 获取上传的文件 解构Arry 拿到第一个元素
const [file] = e.target.files;
if (!file) {
return;
}
const {
type: fileType,
size: fileSize
} = file;
// 上传校验逻辑
if (!ACCEPT.includes(fileType)) {
alert(`不支持[${fileType}]类型`)
upload.value = ""
return
}
if (fileSize > MAXSIZE) {
alert(`文件超${MAXTIP}`)
upload.value = ""
return
}
// 压缩图片 需要转成base64进行压缩
convertImageToBase64(file, (base64Image) => {
compress(base64Image, uploadToServer)
})
})
</script>
</body>

</html>