一, 图片上传前端压缩的现实意义
对于大尺寸图片的上传, 在前端进行压缩除了省流量外, 最大的意义是极大的提高了用户体验.
这种体验包括两方面:
由于上传图片尺寸比较小, 因此上传速度会比较快, 交互会更加流畅, 同时大大降低了网络异常导致上传失败风险.
最最重要的体验改进点: 省略了图片的再加工成本. 很多网站的图片上传功能都会对图片的大小进行限制, 尤其是头像上传, 限制 5M 或者 2M 以内是非常常见的. 然后现在的数码设备拍摄功能都非常出众, 一张原始图片超过 2M 几乎是标配, 此时如果用户想把手机或相机中的某个得意图片上传作为自己的头像, 就会遇到因为图片大小限制而不能上传的窘境, 不得不对图片进行再处理, 而这种体验其实非常不好的. 如果可以在前端进行压缩, 则理论上对图片尺寸的限制是没有必要的.
二, 图片前端 JS 压缩并上传功能体验
特意制作了一个图片前端压缩并上传的完整 demo, 您可以狠狠的点击这里: 使用 canvas 在前端压缩图片并上传 demo
进入 demo 会看到一个相貌平平的文件输入框:
啊, 不对, 应该是这张图:
点击文件选择框, 我们不妨选一张尺寸比较大的图片, 例如下面这种 2M 多的钓鱼收获照:
于是图片歘歘歘地传上去了:
此时我们点击最终上传完毕的图片地址, 会发现原来 2M 多 3000 多像素宽的图片被限制为 400 像素宽了:
保存到本地会发现图片尺寸已经变成只有 70K 了:
以上就是图片前端压缩并上传 demo 的完整演示.
三, html5 file API 加 canvas 实现图片前端 JS 压缩
要想使用 JS 实现图片的压缩效果, 原理其实很简单, 核心 API 就是使用 canvas 的 drawImage()方法.
canvas 的 drawImage()方法 API 如下:
- context.drawImage(img, dx, dy);
- context.drawImage(img, dx, dy, dWidth, dHeight);
- context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
后面最复杂的语法虽然看上去有 9 大参数, 但不用慌, 实际上可以看出就 3 个参数:
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
是在前面的. 如果不注意这一点, 有些表现会让你无法理解.
下图为 MDN https://developer.mozilla.org/en-US/docs/web/API/CanvasRenderingContext2D/drawImage 上原理示意:
对于本文的图片压缩, 需要用的是是 5 个参数语法. 举个例子, 一张图片 (假设图片对象是 img) 的原始尺寸是 4000*3000, 现在需要把尺寸限制为 400*300 大小, 很简单, 原理如下代码示意:
- var canvas = document.createElement('canvas');
- var context = canvas.getContext('2d');
- canvas.width = 400;
- canvas.height = 300;
- // 核心 JS 就这个
- context.drawImage(img,0,0,400,300);
把一张大的图片, 直接画在一张小小的画布上. 此时大图片就天然变成了小图片, 压缩就这么实现了, 是不是简单的有点超乎想象.
当然, 若要落地于实际开发, 我们还需要做些其他的工作, 就是要解决图片来源和图片去向的问题.
1. 如何把系统中图片呈现在浏览器中?
HTML5 file API 可以让图片在上传之前直接在浏览器中显示, 通常使用 FileReader 方法, 代码示意如下:
- var reader = new FileReader(), img = new Image();
- // 读文件成功的回调
- reader.onload = function(e) {
- // e.target.result 就是图片的 base64 地址信息
- img.src = e.target.result;
- };
- eleFile.addEventListener('change', function (event) {
- reader.readAsDataURL(event.target.files[0]);
- });
于是, 包含图片信息的 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()方法
语法 https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/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 压缩图片" 三步曲, 我们完成了图片前端压缩并上传的功能.
更加完整的核心代码请参见 demo 页面的左侧, 如果对其他交互代码也敢兴趣, 请参考页面源代码.
四, 结束语
就在几个月前刚写过一篇文章 "使用 canvas 在前端实现图片水印合成", 实际上所使用的技术和套路和本文是如出一辙的, 也是 "图片canvas 水印图片" 三步曲, 区别在于水印合成是连续执行两次 context.drawImage()方法, 一次是原图一次水印图片, 以及最后转换成图片的时候什么是 toDataURL()方法, 其他代码逻辑和原理都是一样的.
由此及彼, 利用同样的原理和代码逻辑, 我们还可以实现其它很多以前前端不太好实现的功能, 比方说图片的真剪裁效果, 所谓 "真剪裁" 指不是使用个 overflow:hidden 或者 clip 这些 CSS 属性的 "伪剪裁", 而是真正意义上就这么大区域图片信息. 甚至配合一些前端算法, 我们可以直接在前端进行人脸识别, 图片自动美化等一系列功能再上传等等.
原理都是一样的, 都是利用 canvas 作为中间媒介进行处理.
来源: https://www.zhangxinxu.com/wordpress/2017/07/html5-canvas-image-compress-upload/