-
感悟源头
前几天读到掘金大神一篇有深度的文章 为什么视频网站的视频链接地址是blob?
让kk对之前在业务上使用过的blob对象有了深入的了解。所以有感而发,也想要整理一下在之前的场景下,如何运用blob对象对图片进行转码、上传等操作。
-
什么是blob
kk查询了 MDN 对blob的定义:
Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
基于此定义的业务思考,kk可联想到 File对象 就是平常在form表单post形式上传图片文件的所对应的的形式。既然File基于blob,那么post方法上传一个blob对象也是可行的。
-
业务场景
如上图为身份证识别+视频活体检测在H5端实现的一个需求。在用户拍照上传身份证时候,前端要对该图片进行压缩、转码并上传,与此同时保证上传的性能和后端OCR算法的识别精度。
这里顺便想提及下:这个项目在刚开始的时候,上传的性能比较差,原因是上传的图片为原图,容量很大导致上传时间太长,影响了用户体验。
大家也知道,在微信里发送原图也是需要一定时间和资源的,在H5端也不例外。
经过反复试验,iOS的手机(kk试验的是iPhone 7 Plus 后置1200万像素),上传的原图容量为3M,换在最新发布的安卓手机上,情况就变得更加恶劣。设计师那台最新发布的荣耀V20,2000万像素,发送一张原图高达8M,这就尴尬了。这势必要在H5端上传之前对图片进行压缩。
要用到blob的地方就在于压缩过后,图片转码并上传的过程。事不宜迟,上代码。
-
功能实现
功能实现主要分为如下四大块
- 图片选取
- 图片压缩
- blob转码
- 图片上传
-
-
图片选取
-
preview(event) {
var _this = this;
const eventPath = event.path || (event.composedPath && event.composedPath());
// 当选取一张图片时,新版iOS Safari浏览器会将图片存储在composedPath里,或者从composedPath方法获取,而普通浏览器依然会存放于path里
var domId = eventPath[0].id;
var cardType = _this.cardTypeJudge(eventPath[0].id);
let files = document.getElementById(eventPath[0].id).files[0];
if (files.size / 1048576 > 15) {
_this.confirmShow = true;
_this.confirmReason = '图片容量过大,请重新拍摄';
document.getElementById(domId).value = null
return false;
}
_this.smImage(files).then(resone => {
//上传图片
_this.uploadFile(resone, cardType).then(restwo => {
// console.log('restwo:' + restwo);
document.getElementById(eventPath[0].id).value = null;
_this.handleIdCardUpdateSuccess(_this.app.idCardFrontSrc, _this.app.idCardBackSrc, 1, cardType);
_this.submitBtnStatus();
});
});
},
- 解析
在用户从系统相册选定图片后,H5需要获取触发事件元素冒泡过程的所有元素,即点击选择的图片地址。
经过kk分析,在Chrome中可以通过event.path获取,Firefox和Safari中发现event并没有path属性,确实需要调用event.composedPath()方法或者从event.composedPath取得。
这样才能保证H5在各移动端浏览器的功能兼容性。
Event.composedPath() 是浏览器一个新的标准,没想到移动端的更新步伐那么快。
如下是kk参考其他作者整理的获取图片元素地址的方法:
element.onClick(event) {
const ev = window.event || event;
const path = event.path || (event.composedPath && event.composedPath());
console.log(path) //[button#btn, div, body, html, document, Window]
}
-
-
图片压缩
-
smImage(files) {
var _this = this;
return new Promise((resolve, reject) => {
// 压缩图片需要的一些元素和对象
var reader = new FileReader(),
//创建一个img对象
img = new Image();
// 缩放图片需要的canvas
var canvas = document.getElementById("imgcanvas");;
var context = canvas.getContext('2d');
// base64地址图片加载完毕后
img.onload = function () {
// 图片原始尺寸
var originWidth = this.width;
var originHeight = this.height;
// 最大尺寸限制,可通过国设置宽高来实现图片压缩程度
var maxWidth = 1300,
maxHeight = 1300;
// 目标尺寸
var targetWidth = originWidth,
targetHeight = originHeight;
// 图片尺寸超过1300x1300的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth;
targetHeight = Math.round(maxWidth * (originHeight / originWidth));
} else {
targetHeight = maxHeight;
targetWidth = Math.round(maxHeight * (originWidth / originHeight));
}
}
// canvas对图片进行缩放
canvas.width = targetWidth;
canvas.height = targetHeight;
// 清除画布
context.clearRect(0, 0, targetWidth, targetHeight);
// 图片压缩
context.drawImage(img, 0, 0, targetWidth, targetHeight);
/*第一个参数是创建的img对象;第二个参数是左上角坐标,后面两个是画布区域宽高*/
//压缩后的图片base64 url
/*canvas.toDataURL(mimeType, qualityArgument),mimeType 默认值是'image/jpeg';
* qualityArgument表示导出的图片质量,只要导出为jpg和webp格式的时候此参数才有效果,默认值是0.92*/
var newUrl = canvas.toDataURL('image/jpeg', 0.5); //base64 格式
var blob = _this.convertBase64UrlToBlob(newUrl);
// console.log(blob);
// console.log('canvas宽度:' + targetWidth + ' 长度:' + targetHeight);
if (targetWidth < targetHeight) {
_this.isLongImg = true;
} else {
_this.isLongImg = false;
}
resolve(blob);
};
// 文件base64化,以便获知图片原始尺寸
img.src = _this.app.getFilePath(files);
})
}
- 解析
图片压缩总体采用canvas重绘的方法,对图片的尺寸和分辨率两方面压缩,其阈值通过canvas的各种API可实现人工调整。
以上所述,重绘大致分为两步:尺寸限定和压缩绘制。
- 尺寸限定
先创建一个canvas画布,并限制其画布宽高。kk将画布宽maxWidth高maxHeight阈值都限定在了1300px,同时取得图片的原始宽originWidth高originHeight进行比较。
当width和height缩减到同比例下低于宽高限定阈值时,则根据画布宽高drawImage重绘。
- 压缩绘制
主要取决于canvas.toDataURL() 方法中。当图片重绘至画布后,通过此方法可输出一个Data URI,即默认为png的base64串。
压缩的比例,图片输出的分辨率就取决于这个方法的第二个参数:encoderOptions 。
调整这个参数就可以导出不同分辨率的base64串,kk在这里调整的是0.5,即50%分辨率的压缩。
-
-
blob转码
-
上面kk也提及过,使用post方法上传文件,会取得一个File对象(基于blob),并作为二进制流(binary)上传到服务端。
现在重绘的图片呈现的形式是base64,则需要一个base64转blob的方法。
convertBase64UrlToBlob(urlData) {
var arr = urlData.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {
type: mime
});
}
这个方法目前是怎么进行转码的kk还没弄清楚,只是作为Util工具类一直运用在该项目里,后期弄懂后会更新。
-
-
图片上传
-
-
-
阈值调整
-
在此功能流程中,可调整的阈值有:
- 图片尺寸压缩的临界值:maxWidth 和 maxHeight(本质是canvas画布的尺寸)
- 导出图片的分辨率:本质是canvas.toDataURL()的参数设置
阈值调整后,性能相比于原来提升了50%,同时也保证了文字识别OCR的精度(实际上算法精度对图片分辨率的要求并不需要原图那么高,所以进行适当的压缩并不影响)。
性能提升50%体现在两方面:功能提升和体验提升。
- 功能提升
原图容量由8M及以上压缩至2M以下,iOS原图更是压缩至1M以下,容量压缩了70%左右。
- 体验提升
图片上传速度由原来的8.0s左右,提升至现在的2.0s~2.6s(3s以内,也取决于当时的网络状况和服务器的接口响应)。
结语
这是我对这个项目开发经验的整理,以及blob对象运用的一些个人理解。望指正!