背景
对于大尺寸图片的上传,在前端进行压缩除了省流量外,最大的意义是极大的提高了用户体验。
这种体验包括两方面:
- 由于上传图片尺寸比较小,因此上传速度会比较快,交互会更加流畅,同时大大降低了网络异常导致上传失败风险。
- 最最重要的体验改进点:省略了图片的再加工成本。很多网站的图片上传功能都会对图片的大小进行限制,尤其是头像上传,限制5M或者2M以内是非常常见的。然后现在的数码设备拍摄功能都非常出众,一张原始图片超过2M几乎是标配,此时如果用户想把手机或相机中的某个得意图片上传作为自己的头像,就会遇到因为图片大小限制而不能上传的窘境,不得不对图片进行再处理,而这种体验其实非常不好的。如果可以在前端进行压缩,则理论上对图片尺寸的限制是没有必要的。
技术储备
canvas
-
canvas元素可以用来绘制图形以及图形动画
-
canvas暴露HTMLCanvasElement接口,包含了操作canvas元素布局和呈现的方法和属性,其中canvas.drawImage可以在canvas上绘制图片或者图片的一部分
-
可以通过HTMLCanvasElement.getContext(“2d”)获取canvas上下文进行布局,然后通过canvas.toDataURL()返回:data 的URL,或者通过canvas.toBlob()导出一个Blob文件
-
1
2
3
4
5
6
7
8
9function test() { var canvas = document.getElementById("canvas"); var url = canvas.toDataURL(); //canvas.toBlob(function(blob){...}, "image/jpeg", 0.95); // JPEG 质量为95% var newImg = document.createElement("img"); newImg.src = url; document.body.appendChild(newImg); }
blob
- Binary Large Object:二进制数据的大对象;原始数据,无法变更
- 使用字符串,或者对象创建blob,处理方式不同,用对象创建blob对象,会调用普通对象的toString()方法得到字符串数据再去创建blob对象
- 无法对blob对象进行编辑改变,但可以通过slice方法对blob进行分割,生成一个新的blob对象
- 使用场景有分片上传,以及BLob URL作为图片地址或者资源下载地址
blob 应用
url.createObjectURL
- URL接口有两个方法,URL.createObjectURL 创建一个DOMString,包含一个唯一的blob链接, URL.invokeObjectURL 销毁通过createObjectURL创建的URL实例,可以动态创建链接a进行blob下载
1 |
|
FileReader
- fileReader对象允许web内置应用程序读取用户计算机中文件内容,或者原始数据缓冲区中的内容,已File 或者Blob的数据格式读取
- 不能直接通过路径读取,需要使用ajax解决方案进行服务器端文件读取
- 可以通过FileReader.readAsArrayBuffer,readerAsDataURL,readerAsBinaryString,readerAsText等方法读取Blob中内容再以不同形式进行输出
File
- file对象是一种特殊格式的blob,通常,是用户通过input标签选择文件后返回的FileLIst对象,以及其他如DataTransfer对象;
- 可以把Blob文件生成File文件
1 |
|
文件压缩思路
利用HTML5 File API加上canvas实现前端图片JS压缩功能,核心是使用canvas的drawImage方法
1 |
|
-
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()
方法有一个非常怪异的地方,大家一定要注意,那就是5参数和9参数里面参数位置是不一样的,这个和一般的API有所不同。一般API可选参数是放在后面。但是,这里的drawImage()
9个参数时候,可选参数sx,sy,swidth,sheight
是在前面的。如果不注意这一点,有些表现会让你无法理解。
对于本文的图片压缩,需要用的是是5个参数语法。举个例子,一张图片(假设图片对象是img
)的原始尺寸是40003000,现在需要把尺寸限制为400300大小,很简单,原理如下代码示意:
1 |
|
把一张大的图片,直接画在一张小小的画布上。此时大图片就天然变成了小图片,压缩就这么实现了,是不是简单的有点超乎想象。
实践思路
重点要解决图片来源和图片去向的问题
1. 如何把系统中图片呈现在浏览器中?
HTML5 file API可以让图片在上传之前直接在浏览器中显示,通常使用FileReader
方法,代码示意如下:
1 |
|
于是,包含图片信息的context.drawImage()
方法中的img
图片就有了。
2. 如果把canvas画布转换成img图像
canvas
天然提供了2个转图片的方法,一个是:
-
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
,是一个比较合理的图片质量输出参数,通常情况下,我们无需再设定。 -
canvas.toBlob()方法
语法如下:
canvas.toBlob(callback, mimeType, qualityArgument)
可以把canvas转换成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压缩→图片”三步曲,我们完成了图片前端压缩并上传的功能。
1 |
|
CompressorJS源码解读
使用
1 |
|
重点代码
1 |
|
1 |
|
结束语
图片压缩其实和图片合成实际上所使用的技术和套路和本文是如出一辙的,也是“图片→canvas水印→图片”三步曲,区别在于水印合成是连续执行两次context.drawImage()
方法,一次是原图一次水印图片,以及最后转换成图片的时候什么是toDataURL()
方法,其他代码逻辑和原理都是一样的。
由此及彼,利用同样的原理和代码逻辑,我们还可以实现其它很多以前前端不太好实现的功能,比方说图片的真剪裁效果,所谓“真剪裁”指不是使用个overflow:hidden
或者clip
这些CSS属性的“伪剪裁”,而是真正意义上就这么大区域图片信息。甚至配合一些前端算法,我们可以直接在前端进行人脸识别,图片自动美化等一系列功能再上传等等。
原理都是一样的,都是利用canvas
作为中间媒介进行处理。