HTML5 支持的 DataURL 属性

最近某个创业项目要做一个类似于上传头像那种的截图工具(因为招过来的运营智商堪忧,弄一张图片要整好几个小时),由于其他人员对于前端基本残废,那么显然又要我去弄(说到这里,我也不是做前端的啊,为啥不论是公司还是私人项目我都要去帮忙做前端)。

DataURI 早在 1998 年就提出了,可是浏览器支持的时间并不算早。

下面是一个 DataURI,你可以复制它到浏览器的地址栏,然后回车看看。

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAAPCAIAAAAj5aXHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFASURBVEhL7VSxEcIwDGQWGtiFKpvQhEGovEeqrJEK1qBKiWTJystOAhxwQI67L3zvt/wvOVlttutl45/w9zGZsApdfz5WBf8EduF8aQ8ZaeDdvm9qz0zr78U/YcE/gUcdvydh3VIjEyAh8HmbS94VIWgdcWxHulCpnrup4nKGpV74JDs0vv6+1VKqdwnZWbuXNc6Q1laRNVDR6Z0/Ad3nnNHdUgrqJJDXsfYV+rmEFsz0kLA6nrK+Zg4EVpH1aIibN1zM8FYmnQEzMkPYhST381nC4QKXkLdk9BHCzxuSCmnCEV+RcHSGPJxT2KnMKuaG/Aypmh43fDwhJuHL7N1jQl4jb1Py3xVtDc1KeGVCvVceF/DzCeE10h44iH8ReaJdCOgshrQjSsa0xjMk7azjB/T41TS1r3Mr4RKx9ITb9RVdmNetax2r4QAAAABJRU5ErkJggg==

在语法上,冒号后面的字段表示资源的类型,比如 image/png,如果缺省,则为 text/plain。如果要制定文字编码,则应在后面紧跟 charset=<character set> 字段并以分号分隔。最后一部分则是资源的详细内容,如果加上了 base64 以及逗号的前缀则表示以 Base64 进行编码。

这个东东的方便之处在于它可以用字符串的形式表现二进制内容,例如 CSS 里面。于是借助它,我就可以轻松地在网页中实现截图工具。

还记得以前在网页中上传头像,虽然我没研究过其具体实现,但基本上流程是这样:用户选择图片后立即被上传到服务器,之后再在网页中显示出此图像供用户截取一部分。很明显这样做会导致体验不佳,比如服务器只需要一个 300 * 300 的图片,而用户可能选择了一个很大的图片,此时上传就要耗费很大的时间。如果图片能够先在浏览器中处理,那么提交给服务器的就是一个处理好的小尺寸图片,显然就很快了,而且也不会占用服务器资源。

要让浏览器能够直接读取本地文件,需要借助 HTML5 新推出的 FileAPI 接口。FileReader 提供了异步访问本地文件的方法,而它的方便之处就在于能够直接把它以 DataURL 的方式读取出来。我们先做一个上传文件的按钮:

<input type="file" name="image" id="upload-file">

我们使用 FileReader 把文件读取出来。FileReader 可以把文件读取为纯文本、二进制流以及 DataURL 形式。注意这个对象提供的都是异步方法,我们需要绑定它的 load 事件以接受读取完毕的回调。

	$('#upload-file').on('change',function(){
		var reader = new FileReader();
		reader.onload = function(e) {
			originalDataUrl = e.target.result;
			drawPicture();
		}
		reader.readAsDataURL(this.files[0]);
	});

接着我们就需要把这个图片显示出来以供用户缩放并截取。那么我们可以用 Canvas 来绘图(话说我很喜欢 Canvas 这种东西啊,包括 Windows 上的 GDI+,使用起来很方便呢)。Canvas 的上下文对象需要一个 Image 类型的对象来绘制图片,因此我们需要将我们读取到的 DataURL 转换成 Image 对象。我们直接设置 Image 的 src 就可以了,此时浏览器就会开始读取图片内容。与 FileReader 类似,Image 的读取过程也是异步的,读取完毕后会触发 onload 事件。(当然如果你把 src 设置成网络图片也是可以的,同样也会触发 onload 等事件。)

	var canvas = document.getElementById('tempCanvas');
	var context = canvas.getContext('2d');
	var img = new Image();
	img.onload = function() {
		context.drawImage(img, px, py, MAX_WIDTH * aspect, MAX_WIDTH * aspect, 0, 0, 640, 640);
	}
	img.src = originalDataUrl;

你看,我们直接把 DataURL 赋值给了 src 属性。这样,就做到了读取本地文件并显示在页面上。

接着就可以做截取的功能了,这个很容易,绑定一些鼠标事件并根据缩放比例稍微计算一下就可以了, so easy。

当用户编辑完后,Canvas 上所显示的内容同样可以直接转换成 DataURL:

var dataUrl = $('#tempCanvas')[0].toDataURL();

这里 jQuery 查询的结果是一个数组,如果直接 toDataURL 就会出错。

不过呢,发给服务器的 FormData 是一个二进制流(Blob 对象),而 Javascript 里面缺没有直接提供把 DataURL 转换成二进制流的方法。因此这一部分内容还需要我们自己实现(参考自这里):

function dataURItoBlob(dataURI) {
    // convert base64/URLEncoded data component to raw binary data held in a string
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = unescape(dataURI.split(',')[1]);

    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    return new Blob([ia], {type:mimeString});
}
✏️ 有任何想法?欢迎发邮件告诉老夫:daozhihun@outlook.com