从web图片裁剪出发:了解H5中的Blob
刚开始做前端的时候,有个功能卡住我了,就是裁剪并上传头像。当时两个方案摆在我面前,一个是flash,我不会。另一个是通过iframe上传图片,然后再上传坐标由后端裁剪,而我最终的选择是后者。有人会疑惑,为什么不用H5的Canvas和FormData,第一要考虑ie8的兼容性,第二那时候眼界没到,这种新东西光是听听都怕。
后来随着Mobile项目越做越多,类似的功能开发得也越来越多,Canvas+FormData成为了标配方案。但做的多了却一直没有静下心来研究,浏览器怎么使用H5的方式裁剪并把文件发送出去,回过头看都是知其然不知其所以然。这篇随笔先做个初步的拆解,就是当通过input选择一张图片后,这张图片在浏览器里是怎样的一个存在。
文件操作一直是早期浏览器的痛点,全封闭式,不给JS操作的空间,而随着H5一系列新接口的推出,这个壁垒被打破。对,是一系列接口,以下会涉及到如下概念:Blob、File、FileReader、ArrayBuffer、ArrayBufferView、DataURL等,其他如FormData、XMLHttpRequest、Canvas等暂不深入。
我们先创建一个简单的页面,只有一个input[type=file]。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input type="file">
</body>
</html>
然后我们在JS中获取这个元素
var input = document.querySelector('input[type=file]');
可以看到这个元素有个属性files,它的类型是FileList。这个类不做过多介绍,就是一个类数组,由浏览器通过用户行为往里面添加或删除元素,JS只有访问其元素的接口,无法对其进行操作。而files的元素就是File类型,File是blob的子类,比blob主要多出一个name的属性。
现在我们选取一个文件,这里问题来了,这个元素是文件在浏览器的完整备份,还是一个指向文件系统的引用?答案是后者,我们选定文件,然后修改文件名,再上传文件,浏览器报错了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form name='test'>
<input type="file">
<input type="submit" value="提交">
</form>
<script>
var input = document.querySelector('input[type=file]'),
form = document.test;
form.addEventListener('submit', function(e) {
e.preventDefault();
var file = input.files[0],
fd = new FormData(),
xhr = new XMLHttpRequest();
fd.append('file', file);
xhr.open('post', '/upload');
xhr.send(fd);
});
</script>
</body>
</html>
使用chrome打开chrome://blob-internals/,可以看到一条这样的记录
可见这仅仅是一条引用。第二个问题来了,如果我们要对图片进行处理,那么只拿到引用是不行的,肯定要在浏览器有一份数据的备份,那么怎么获取这个备份呢?答案就是FileReader,FileReader的对象主要有readAsArrayBuffer、readAsBinaryString、readAsDataURL、readAsText等方法,它们的入参都是Blob对象或是File对象,结果对应最终获取的数据类型。这几个方法是异步的,读取过程中会抛出对应的事件,其中读取完毕的事件为load,所以数据的处理要放在onload下。我先给一个简单的example:
input.addEventListener('change', function() {
var file = this.files[0],
fr = new FileReader(),
blob;
fr.onload = function() {
blob = new Blob([this.result]);
};
fr.readAsArrayBuffer(file)
});
当用户选取图片时,调用FileReader的readAsArrayBuffer把图片数据读出来,然后生成新的blob对象保存在浏览器中。查看chrome://blob-internals/,可以注意到这一项:
对应的就是刚才的blob,可以对比length和图片本身的大小。上面那个demo很突兀,完全没有解释什么是ArrayBuffer,为什么创建blob要传入一个ArrayBuffer。那么第三个问题来了,什么是ArrayBuffer、BinaryString、DataURL、Text,它们有什么联系和不同,Blob类到底是个什么东西?首先,图片是个二进制文件,它的内容也是由0和1组成的。用户肯定是看不懂0和1的组合的,能看懂的只有最终展示的图片,而程序员也看不懂0和1,但程序员能看懂另外几种0和1变换后的组合。它们就是以上的4种:ArrayBuffer、BinaryString、DataURL和Text。
其中ArrayBuffer是最接近二进制数据的表现的,可以理解为它就是二进制数据的存储器,这也是为什么二进制文件的Blob需要传入ArrayBuffer。正因为它的内部是二进制数据,所以我们是不可以直接操作的。这时候就需要一个代理者帮助我们读或写,这个代理者就是ArrayBufferView。
ArrayBufferView不是一个类,而是一个类的集合,包括:Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array和DataView,分别表示以8位、16位、32位、64位数字为元素对ArrayBuffer内的二进制数据进行展现,它们都有统一的属性buffer指向对应的ArrayBuffer。栗子暂时不举,之后会用到。
ArrayBuffer简单介绍了,那什么是BinaryString呢?是二进制数据直接以byte的形式展现的字符串,比如1100001,用Uint8表示就是97,用BinaryString表示就是'a'。对,前者是charCode,后者是char,所以BinaryString和Uint8Array之间是可以自由转换的。
接下来是DataURL了,这是一个经过base64编码的字符串,它的组成如下:
data:[mimeType];base64,[base64(binaryString)]
除了固定的字符串部分,它主要包含两个重要信息即中括号括起的部分,mimeType和base64编码后的binaryString,从它里面我们可以这样取到这两个信息。
var binaryString = atob(dataUrl.split(',')[1]),
mimeType = dataUrl.split(',')[0].match(/:(.*?);/)[1];
最后,Text是什么呢?在ftp上,文本传输和二进制传输的区别是什么,那Text类型和BinaryString类型的区别就是什么了,也就是Text类型是经过一定转换的BinaryString,对于图片来说,这个类型是用不到的。
好了,现在我们了解了一张图片在浏览器里以数据的形式可以表现为ArrayBuffer、BinaryString、DataURL,那么第四个问题来了,它们各有实际用途呢?我们从应用场景出发,回到文章开头的问题,图片的裁剪和上传。图片的裁剪我们要倚仗牛逼的canvas,而canvas的context有这么一个方法toDataURL,就是把canvas的内容转换为图片数据,而数据的表现形式就是DataURL!图片的上传我们用的是FormData,它可以添加Blob类型的对象进去,那Blob类型除了从input[type=file]中直接获取,还能靠什么生成呢?自然是ArrayBuffer!好了,裁剪图片的功能要用到DataURL,上传图片的功能要用到ArrayBuffer,那怎么从DataURL转换为ArrayBuffer呢?我们知道DataURL很重要的组成部分就是经过base64编码的BinaryString,那么很显然我们可以从DataURL中提取BinaryString,而BinaryString就是ArrayBuffer对应的Uint8Array的字符形式的表现,所以可以由BinaryString生成ArrayBuffer,那么DataURL到ArrayBuffer之间的桥就是BinaryString!
到现在为止,我们说了很多概念,然而这并没有什么卵用,验证概念的方法不是提出新的概念,而是建立一个example。以下的example就是把图片数据从input中取出,然后以DataURL的格式进行预览,提交时把预览生成图片上传的整个流程。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form name='test'>
<input type="file" name='file'>
<input type="submit" value="提交">
</form>
<img src="" alt="">
<script>
var img = document.querySelector('img'),
preview;
document.test.file.addEventListener('change', function() {
var fr = new FileReader();
fr.onload = function() {
preview = this.result;
img.src = preview;
};
fr.readAsDataURL(this.files[0]);
})
document.test.addEventListener('submit', function(e) {
e.preventDefault();
var binaryString = atob(preview.split(',')[1]),
mimeType = preview.split(',')[0].match(/:(.*?);/)[1],
length = binaryString.length,
u8arr = new Uint8Array(length),
blob,
fd = new FormData(),
xhr = new XMLHttpRequest();
while(length--) {
u8arr[length] = binaryString.charCodeAt(length);
}
blob = new Blob([u8arr.buffer], {type: mimeType});
fd.append('file', blob);
xhr.open('post', '/upload');
xhr.send(fd);
})
</script>
</body>
</html>
现在图片已经被我们发射出去了,那么图片在协议包里是以怎样的数据形式存在的呢?当然是以二进制的形式,我们抓一下包,发现在fiddler里面这个二进制串会转换为字符串,即上面的binaryString。
既然通过发送的blob到最后在数据包里都是以binaryString的形式展示,那么是否可以直接使用xhr.send(binaryString)发送图片呢?貌似是可以的,但我们试一下就会发现问题,服务器获取到的信息不能生成一张图片,说明数据被破坏了。那么数据是谁破坏的呢?这个罪魁祸首就是send,当send的参数是字符串的时候,会对字符串进行utf8编码。我们看下相同的图片通过blob发送出去和通过binaryString直接发送出去的数据会有什么不同。这里我们用wireshark抓包,因为wireshark会自动对数据块进行分割,可以比较直观的看到图片所对应的数据。PS: 这张图片一张1px白色的png。
前面是正常的图片数据,后面是经过了utf8编码的图片数据。我们可以看到数据确实被破坏了,当然在知道元数据是binaryString的情况下,这种破坏是可以恢复的,不过不是这里讨论的范畴了,感兴趣的可以跳转阮老师的博客《字符编码笔记:ASCII,Unicode和UTF-8》。
好了,整个图片在浏览器端的拆解到此结束。理解了这些,就走完了写出牛逼的客户端图片裁剪工具的第一步。
从web图片裁剪出发:了解H5中的Blob的更多相关文章
- 从web图片裁剪出发:了解H5中的canvas
本篇内容不针对canvas文档对每个api进行逐个的详解! 本篇内容不针对canvas文档对每个api进行逐个的详解! 本篇内容不针对canvas文档对每个api进行逐个的详解! 重说三,好了,现在进 ...
- Android大图片裁剪终极解决方案(上:原理分析)
转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-) http://my.oschina.net/ryanhoo/blog/86842 约几个月前,我正 ...
- web开发实战--图片裁剪和上传
前言: 最近的开发中, 有一个上传头像的任务. 由于头像本身的特殊性, 其一般流程为选择图片, 编辑裁剪区域, 再继而上传图片操作. 看似简单的东西, 实则是挺麻烦的一件事. 借助这次开发机会, 来具 ...
- H5图片裁剪升级版
前段时间做了个跟裁剪相关的活动<用H5中的Canvas等技术制作海报>,这次公司要做个与奥运相关的活动,扫车牌赢奖. 于是我就在上一个活动的基础上,将代码重新封装一下,并且将计算方式写的更 ...
- H5图片裁剪升级版(手机版)
前段时间做了个跟裁剪相关的活动<用H5中的Canvas等技术制作海报>,这次公司要做个与奥运相关的活动,扫车牌赢奖. 于是我就在上一个活动的基础上,将代码重新封装一下,并且将计算方式写的更 ...
- 移动端 H5图片裁剪插件,内置简单手势操作
前面曾经写过一篇<H5图片裁剪升级版>,但里面需要借助第三方手势库,这次就不需要使用手势库,全部封装在代码中. 下图是裁剪的展示,下面就做了拖放和裁剪,没有做缩放,在插件中需要用到大量的计 ...
- apiCloud中图片裁剪模块FNImageClip的使用
思路 1.获取需裁剪图片的地址 2.跳转到裁剪页面 3.裁剪成功返回新图片地址 4.替换原有图片地址 增加修饰和事件 str += '<li class="tu image" ...
- 微信中web页面实现和公众号中查看图片一样的效果
最近开发了一套资讯相关的web页面,嵌套在微信中,可支持点赞.评论等...在文章详情中,图片需要点击放大,随手势放大缩小,左右可滑动切换,总之类似于微信公众号效果. 开始想的方案是用轮播插件.或者在i ...
- H5移动端图片裁剪(base64)
在移动端开发的过程中,或许会遇到对图片裁剪的问题.当然遇到问题问题,不管你想什么方法都是要进行解决的,哪怕是丑点,难看点,都得去解决掉. 图片裁剪的jquery插件有很多,我也测试过很多,不过大多数都 ...
随机推荐
- SpringBoot实战总汇--详解
简介 Spring 诞生时是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的 轻量级代替品.无需开发重量级的 Enterprise JavaBean(EJ ...
- Cocoapods 应用第一部分 - Xcode 创建 .framework 相关
问题的提出: 随着项目的越来越大,可能会出现好几个团队共同维护一个项目的情况,例如:项目组A负责其中的A块,项目组B负责其中的B块.....这几块彼此之间既独立,也相互联系.对于这种情况,可以采用约定 ...
- numastat的理解
numa的统计数据及理解如下, [root@localhost kernel]# numastat node0 node1numa_hit ...
- C#+HtmlAgilityPack—>糗事百科桌面版V2.0
最近在浏览以前自己上传的源码,发现在糗事百科桌面端源码评论区中,有人说现在程序不能用了.查看了一下源码运行情况,发现是正则表达式解析问题.由于糗百的网页版链接和网页格式稍有变化,导致解释失败.虽然可以 ...
- [ZYNQ-7]PS处理PL外部中断的简单实例的剖析 (参考米联miz702n)
Zynq的ARM通过GIC中断控制器来接收核仲裁所有的中断.由于中断向量表只有4Bytes大小,仅仅正好存放一条跳转语句,因此当产生一个外部中断时,中断处理的大致过程:PC内容保存到LR_IRQ用于中 ...
- VBS脚本插入excel图片
--VBS脚本插入excel图片 -------------------------2013/11/23 根据第一列的值,需找对应的图片,然后插入的指定的列中,图片根据列的长宽信息决定图片大小. 代码 ...
- linux守护进程、SIGHUP与nohup详解
前端时间帮忙定位个问题.docker容器故障恢复后,其中的keepalived进程始终无法启动,也看不到Keepalived的日志. strace 查看系统调用之后,发现了原因所在 socket(PF ...
- testng相关的Annotation注释方法,
2 - Annotation这里是TestNG中用到的annotation的快速预览,还有它们的属性. @BeforeSuite: 被注释的方法将在所有测试运行前运行,方法将只运行一次@AfterSu ...
- SDWebImage4.0.0 源码解析
在开发iOS的客户端应用时,经常需要从服务器下载图片,虽然系统提供了下载工具:NSData.NSURLSession等等方法,但是考虑到图片下载过程中,需要考虑的因素比较多,比如:异步下载.图片缓存. ...
- 运维之Linux基础(二)
运维之Linux基础(二) 1. file 命令基期用法 2. 文件系统 Linux的文件系统结构是树状结构,所有的文件都在/root跟目录下 /boot:系统启动相关的文件, 如:内核.initrd ...