实现原理
要想使用JS实现图片的压缩效果,核心API就是使用canvas
的drawImage()
方法。Canvas本质上就是一张位图,而drawImage()方法可以把一张大大的图片绘制在小小的Canvas画布上,不就等同于图片尺寸压缩了?对于本案例的压缩,使用的5个参数的API方法
context.drawImage(img, dx, dy);
context.drawImage(img, dx, dy, dWidth, dHeight);
context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
img
就是图片对象,可以是页面上获取的DOM对象,也可以是虚拟DOM中的图片对象。
dx, dy, dWidth, dHeight
表示在canvas画布上规划处一片区域用来放置图片,dx, dy为canvas元素的左上角坐标,dWidth, dHeight指canvas元素上用在显示图片的区域大小。如果没有指定sx,sy,sWidth,sHeight这4个参数,则图片会被拉伸或缩放在这片区域内。
sx,sy,swidth,sheight
这4个坐标是针对图片元素的,表示图片在canvas画布上显示的大小和位置。sx,sy表示图片上sx,sy这个坐标作为左上角,然后往右下角的swidth,sheight尺寸范围图片作为最终在canvas上显示的图片内容。
drawImage()
drawImage()方法有一个非常怪异的地方,大家一定要注意,这里的drawImage()9个参数时候,可选参数sx,sy,swidth,sheight是在前面的。
下图为MDN上原理示意:
本文的图片压缩,需要用的是是5个参数语法。 一张图片(假设图片对象是img)的原始尺寸是originWidth *originHeight ,现要求尺寸最大宽度为500, 原理如下代码示意:
// 创建画布
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// 获取图片宽高
const originWidth = that.width
const originHeight = that.height
// 最大宽度
const maxWidth = 500
let targetWidth = originWidth
let targetHeight = originHeight
// 超出之后进行处理
if (originWidth > maxWidth) {
targetWidth = maxWidth
targetHeight = Math.round(originHeight * (maxWidth / originWidth))
}
// 设置画布宽高
var anw = document.createAttribute("width");
anw.nodeValue = targetWidth;
var anh = document.createAttribute("height");
anh.nodeValue = targetHeight;
canvas.setAttributeNode(anw);
canvas.setAttributeNode(anh);
//绘制图片
context.drawImage(that, 0, 0, targetWidth, targetHeight);
核心JS
把大图片画在一张小画布上,压缩就这么实现了。
context.drawImage(that, 0, 0, targetWidth, targetHeight);
三、上传或下载
上传图片或者下载图片,可以使用canvas.toDataURL()
或者canvas.toBlob()
方法先进行转换。
1. canvas.toDataURL()
语法如下:
canvas.toDataURL(mimeType, qualityArgument)
可以把画布转换成base64格式信息图像信息,纯字符的图片表示法。
其中:mimeType表示canvas导出来的base64图片的类型,默认是png格式,也即是默认值是’image/png’,我们也可以指定为jpg格式’image/jpeg’或者webp等格式。file对象中的file.type就是文件的mimeType类型,在转换时候正好可以直接拿来用(如果有file对象)。qualityArgument表示导出的图片质量,只要导出为jpg和webp格式的时候此参数才有效果,默认值是0.92,是一个比较合理的图片质量输出参数,通常情况下,我们无需再设定。
//压缩比例
var quality = 0.8;
var base64 = canvas.toDataURL('image/jpeg', quality);
2. canvas.toBlob()方法
语法如下:
canvas.toBlob(callback, mimeType, qualityArgument)
可以把画布转换成Blob文件,通常用在文件上传中,因为是二进制的,对后端更加友好。和toDataURL()方法相比,toBlob()方法是异步的,因此多了个callback参数,这个callback回调方法默认的第一个参数就是转换好的blob文件信息,本文一开始的demo案例中的文件上传就是将canvas图片转换成二进制的blob文件,然后再ajax上传的,代码如下:
// canvas转为blob并上传
canvas.toBlob(function (blob) {
// 图片ajax上传
var xhr = new XMLHttpRequest();
// 开始上传
xhr.open("POST", 'upload.php', true);
xhr.send(blob);
});
总结
经过“图片→canvas压缩→图片”三步曲,我们完成了图片前端压缩功能。IOS照片旋转问题参考文章:iOS手机竖着拍的照片被旋转了90°的原因以及解决方案
部分代码:
引入2个JS文件:(文件已上传)
import * as EXIF from ‘@/common/js/exif.js’;
import * as TranslateImage from ‘@/common/js/translate-image.js’
//图片选择
chooseImage: async function() {
uni.chooseImage({
sourceType: ['camera', 'album'],
sizeType: ['compressed'],
count: 4,
success: (res) => {
this.pzfx(res.tempFilePaths)
}
})
},
// 获取图片方向信息
pzfx(url) {
var that = this
var img = new Image();
img.src = url;
EXIF.getData(img, function() { // IMG_FILE为图像数据
var orientation = EXIF.getTag(this, "Orientation");
that.Base64(url, orientation)
});
},
// 压缩图片
Base64(img, orientation) {
//非APP平台不支持自定义压缩,暂时没有处理,可通过uni-app上传组件的sizeType属性压缩
TranslateImage.translate(img, orientation, (url) => {
this.imgList = this.imgList.concat(url)
let images = this.imgList.map((value, index) => {
return {
imageName: "image" + index,
imgUrl: value
}
})
/* 临时图片列表*/
this.imagetempList = this.imagetempList.concat(images)
this.imagetempList = this.unique(this.imagetempList);
/* 所有图片列表*/
this.imageList = this.imageList.concat(images);
this.imageList = this.unique(this.imageList);
})
},
//图片去重
unique(arr) {
const res = new Map();
return arr.filter((arr) => !res.has(arr.imageName) && res.set(arr.imageName, 1));
},
//图片删除
closeImg(e) {
var index0 = this.imageList.findIndex(item => {
if (item.imageName == e.imageName) {
return true
}
})
this.imageList.splice(index0, 1)
var index1 = this.imagetempList.findIndex(item => {
if (item.imageName == e.imageName) {
return true
}
})
this.imagetempList.splice(index1, 1)
var index2 = this.imgList.findIndex(item => {
if (item.imageName == e.imageName) {
return true
}
})
this.imgList.splice(index2, 1)
},