在前端实现图片压缩上传

最近在整理之前实现的一些插件,刚好碰到了图片压缩上传这个问题,在移动端中,由于网速有限而手机照片往往比较大,导致图片上传比较缓慢,当时是通过HTML5中文件和二进制数据接口,配合Canvas实现在前端进行图片压缩的。这里稍作整理,权当复习。

<!--more-->

整个项目放在github上面了。操作流程大致如下:

  • 获取到input[type=file]上传的图片文件
  • 将图片文件绘制到canvas画布上
  • 将canvas画布再转换成新的图片(此过程进行压缩),上传重新生成的图片

这里需要先了解浏览器提供的相关接口。

1. 相关对象接口

这里基本上都是补MDN文档上的基础知识了,只能说MDN太赞了!

1.1. Blob对象

Blob是一种JavaScript对象类型,其作用是存储大量二进制数据

// 返回一个blob对象,array参数可以是
// ArrayBuffer
// ArrayBufferView
// Blob
// DOMString
// 其他类似对象的混合体

// 来自官方文档
var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // an array consisting of a single DOMString
var oMyBlob = new Blob(aFileParts, {type : 'text/html'});
console.log(oMyBlob);

常用的场景比如对较大文件进行分割(使用blob.slice()方法)。

1.2. FileList

FileList对象通常来自一个 input元素的files属性

<input type="file" id="imgFile" multiple>

imgFile.onchange = function () {
    console.log(this.files); 
    // 选择三张图片,获得结果
    // FileList {0: File, 1: File, 2: File, length: 3}
}

可以看见FileList是一个类数组对象,且每个元素都是一个File对象

1.3. File

File对象包含文件的基本信息,以及文件内容的存取方法。此外,,File对象继承了Blob接口,因此也可以使用更多blob对象相关的方法。

我们这里需要处理的图像,就是一个file对象,file对象常用的属性有name(文件名),size(文件尺寸),type(MIME类型),至于相关的方法已经转移到了FileReader对象中了。

1.4. FileReader

HTML5允许JavaScript在用户上传文件之后在读取这些文件的内容 FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容。可以通过FileReader()构造函数来创建一个filereader对象。

// 可以使用下面的方法来读取某个文件的内容
// 这些方法的参数均为file或blob对象
fr.readAsArrayBuffer(file); // ArrayBuffer对象表示的文件内容
fr.readAsBinaryString(file); // 原始二进制表示的文件内容
fr.readAsDataURL(file); // data: URL表示的文件内容
fr.readAsText(file); // 字符串表示的文件内容
// 此外需要注意这些方法都是异步的,当读取完毕内容存放在fr的result属性中
fr.onloadend = function (e) {
    console.log(fr.result);
};

// 取消文件类容的读取
fr.abort();
// 另外还有abort, load, loadstart, progress等事件

1.5. URL对象

URL对象代表一个资源标识符对象,主要实现了URLUtils 中定义的属性,但是我们这里先只关注与文件相关的方面,也就是URL构造函数的两个静态方法:

// 返回一个字符串,它的URL表示参数中的file或者blob对象,每次调用都会创建一个新的 URL 对象(即使参数相同)
var objectURL = URL.createObjectURL(blob);

//  创建的URL的生命周期和创建它的窗口中的 document 绑定,浏览器会在文档退出的时候自动释放它们,但是为了效率可以手动释放
URL.revokeObjectURL(objectURL);

1.6. FromData

FormData对象主要用来组装一组用 XMLHttpRequest发送请求的键/值对,可以更加灵活地发送Ajax请求。

可以使用FormData构造函数来获取一个fd对象:

// 通过`FormData()`构造函数获取一个空对象
var fd = new FormData();
// 也可以传入一个表单元素作为构造参数
var fd = new FormData(formElm);

fd对象中可以包含多种类型的数据,包括:

// 普通key-value键值对
fd.append("user", "txm");

// 文件对象
formData.append("userfile", fileInputElement.files[0]);

需要注意的是尽管调用了append方法增添数据,但是打印fd仍然显示问一个空对象。

2. 相关概念和思路

2.1. base64编码

Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,可用于在HTTP环境下传递较长的标识信息,Base64编码普遍应用于需要通过被设计为处理文本数据的媒介上储存和传输二进制数据而需要编码该二进制数据的场景(比如我们这里的异步传图片)。这样是为了保证数据的完整并且不用在传输过程中修改这些数据。 在网络传输中,将二进制文件(blob)保存为Base64编码的文本,这些文本可以内嵌在XML的标签中,因此二进制信息它可以随着XML文件被拷贝、下载而不用担心信息会缺失。

// 对base64字符串进行解码,a表示ASCII,b表示base64
atob()
// 对blob对象进行编码
btoa()

2.2. Canvas压缩图片

参考文档:

主要的思路是使用drawImage()将上传图片绘制到画布上,再使用toDataURL()将画布另存为图片,通过指定质量达到压缩的效果。这个压缩效果还是十分明显的。

    function minify(file, cb, quality) {
        quality = quality || 0.5;
        // 压缩图片
        function minifyImg(source, quality) {
            var w = source.width;
            var h = source.height;

            var canvas = document.createElement("canvas");
            canvas.width = w;
            canvas.height = h;

            var ctx = canvas.getContext("2d");
            // 取出png转jpg的黑色背景
            ctx.fillRect(0, 0, w, h);

            // 将原图绘制在画布上,然后再将画布导出为图片,达到压缩的效果
            ctx.drawImage(source, 0, 0);
            return canvas.toDataURL("image/jpeg", quality);
        }


        var imgURL = URL.createObjectURL(file);

        var source = new Image();
        source.src = imgURL;
        source.onload = function() {
            var imgData = minifyImg(this, quality);

            if (typeof cb === "function") {
                cb(imgData);
            }
        };
    };

3. 图片异步上传

既然获取到了压缩后的图片资源,上传过程就比较简单了。

将base64文本传递到后台,后台将文本还原成图片即可。下面是PHP的简单实现,相关接口只需要返回保存后的图片路径即可

$imgData = $_REQUEST['imgData'];
$base64 = explode(',', $imgData)[1];
$img = base64_decode($base64);
$url = './test.jpg';
if (file_put_contents($url, $img)) {
    exit(json_encode(array(
        url => $url
    )));
}