HTML5 客户端图片压缩

问题

上传图片,在传统的 HTML 前端处理时,这是一个经常遇到的问题。在手机端应用普及的今天,随手可以拍照,上传图片的应用场景更成了家常便饭。

但是现在手机随便拿一个都起码 500万像素,动辄几M甚至十几M数十M的照片,要上传怎么办?无论是流量还是网速,都很成问题

旧的处理办法,要上传一个图片,我们先得放一个 <input type="file" /> 的字段,然后通过 POST 方式,将选择的文件二进制传到服务器端,然后服务器端在来做所有的逻辑操作。

包括显示、限制大小、裁剪、存储等等操作,必须先传输到后台!这对于脆弱的移动端流量,乃至即使是普遍的 50KB/s 的 ADSL 上传带宽,都是然并卵

拍得好图有什么用,上传不了。

解决方案

还好,在 HTML5 的支持下,我们可以从客户端获取到选择的上传文件的二进制 BLOB 内容,从而可以在客户端对其做任意的操作!

可以参照本博此前的文章,可以实现在上传图片之前在客户端显示其内容:https://www.huangwenchao.com.cn/2015/03/html5-image-preview.html

其实对于压缩和上传而言,只是多了一步操作,各步骤逻辑如下:

  1. 选择图片,触发上传控件的 change 事件;
  2. 通过事件的 event.target.files 对象,获取到选择的文件;
  3. 通过 file.size 预判文件大小是否超界;
  4. 通过 window.URL 或者 window.webkitURLcreateObjectURL 创建图片资源对象;
  5. <!-- 以上是此前图片展示里面说明的部分内容,下面是压缩 -->
  6. 创建 canvas,将图片绘制到 canvas 中;
  7. 对 canvas 进行裁切缩放,以将图片的像素尺寸压缩到合理的范围(动辄超500万像素是没必要的);
  8. 对 canvas 上面的图片执行压缩,通过 png 或者 jpeg 压缩方法导出文件的二进制内容;
  9. 将压缩之后的图片二进制内容放回表单再销毁过程中使用的资源(img/canvas/window.URL等等)。

代码解析

首先我们需要有一个这样的 markup:

<input type="file" id="upload_image" />
<input type="hidden" id="compressed" name="image" />

仅此而已,然后我们要在选择文件之后触发一系列的动作:

var $upload = $('#upload_image').change(function(event) {
    var files = event.target.files, file;
    if(files && files.length > 0 && images_num < MAX_IMAGES_NUM) {
        file = files[0];
        var URL = window.URL || window.webkitURL;
        if(!URL) {
            alert('你的浏览器不支持客户端图片处理!');
            return false;
        }
        var imgURL = URL.createObjectURL(file);
        $('#compressed').val(getCompressedImageBase64(imgURL));
    }
});

上面是前面步骤的 1-4 步,通过这个方法,我们已经获得了 imgURL 这个可以通过客户端加载图片的链接(注意这个 URL 的形式大概是 blob://... 这个样子,完全是没有触及到任何的网络传输功能的!)。

好了,我们现在来实现 getCompressedImageBase64(imgURL) 这个函数,这个函数接受一个合法的 image 资源 url,然后将其压缩,并且返回压缩之后的图片的 base64 编码内容。

var getCompressedImageBase64 = function(imgURL) {
    var $img = $('<img>');
    $img.one('load', function() {
        var c = document.createElement('canvas');
        var ctx = c.getContext('2d');
        var w0 = $img.width();
        var h0 = $img.height();
        // 假设要将宽度压缩到 1024,那么目标宽度就是 w=1024,目标高度就是 h=h0/w0*1024
        c.width = 1024;
        c.height = h0/w0*1024;
        // 填上白色背景,否则对于透明图会有 bug
        ctx.fillStyle = '#FFFFFF';
        ctx.fillRect(0, 0, c.width, c.height);
        // 将图片渲染上去
        ctx.drawImage($img[0], 0, 0, c.width, c.height);
        // 通过 HTML5 编码压缩并且获取 base64,这里使用 jpeg 压缩,可能不太理想,但起码能够缩小体积,png 也可以,但文件体积无法缩小
        var img_data = c.toDataURL('image/jpeg', 0.5); 
        // 释放资源
        URL.revokeObjectURL(imgURL);
        return img_data;
    });
    $img.attr('src', imgURL);
};

注意 c.toDataURL 这个方法是较新版浏览器才支持的编码方法,有些浏览器还是不支持的(大名鼎鼎的微信浏览器目前实测无法压缩 jpeg,只能不压缩导出 png)。

为了解决上述问题,我特意在 StackOverflow 上面找到了问题 http://stackoverflow.com/a/19286473/2544762 并且得到一个很好的答案。

我们可以从 ctx 里面直接通过 getImageData 方法获得图形矩阵的二进制数据,然后通过这个第三方 js 类库来将其压缩编码成 jpeg 的 base64 编码,由于这个是纯粹手动实现,因此兼容性极好:

var img_data = ctx.getImageData(0, 0, c.width, c.height);
var encoder = new JPEGEncoder();
img_data = encoder.encode(img_data);

这个 JPEGEncoder 的类库这里也直接提供一下:

https://gist.github.com/fish-ball/e30ca9d654cdcbebf86e

<script src="https://gist.github.com/fish-ball/e30ca9d654cdcbebf86e.js"></script>

纯粹借花敬佛,非本人发明,仅为便于查看使用,具体来源请参见上面 Stackoverflow 里面的问题。

至此问题基本已经解决,后面在后台还需要对接收到的 base64 格式文件设法解开一下即可。

后记

其实做到这里,已经很接近可以把一个 file 控件做成是自动压缩的了,假如我们可以将图片压缩之后,再写回 files 对象里面,又或者我们可以模拟到 multipart/form 的 HTTP body 提交模式,按照同样的规则设置 post 表单,可能从接口上可以做到透明,这样子就可以把这部分工作封装成一个控件了,这个暂且留个尾巴,日后机缘到了再行补充。


【转载请附】愿以此功德,回向 >>

原文链接:https://www.huangwenchao.com.cn/2015/08/html5-client-image-compress.html【HTML5 客户端图片压缩】

《HTML5 客户端图片压缩》有3个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注