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