刚开始做前端的时候,有个功能卡住我了,就是裁剪并上传头像。当时两个方案摆在我面前,一个是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的更多相关文章

  1. 从web图片裁剪出发:了解H5中的canvas

    本篇内容不针对canvas文档对每个api进行逐个的详解! 本篇内容不针对canvas文档对每个api进行逐个的详解! 本篇内容不针对canvas文档对每个api进行逐个的详解! 重说三,好了,现在进 ...

  2. Android大图片裁剪终极解决方案(上:原理分析)

    转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-)  http://my.oschina.net/ryanhoo/blog/86842 约几个月前,我正 ...

  3. web开发实战--图片裁剪和上传

    前言: 最近的开发中, 有一个上传头像的任务. 由于头像本身的特殊性, 其一般流程为选择图片, 编辑裁剪区域, 再继而上传图片操作. 看似简单的东西, 实则是挺麻烦的一件事. 借助这次开发机会, 来具 ...

  4. H5图片裁剪升级版

    前段时间做了个跟裁剪相关的活动<用H5中的Canvas等技术制作海报>,这次公司要做个与奥运相关的活动,扫车牌赢奖. 于是我就在上一个活动的基础上,将代码重新封装一下,并且将计算方式写的更 ...

  5. H5图片裁剪升级版(手机版)

    前段时间做了个跟裁剪相关的活动<用H5中的Canvas等技术制作海报>,这次公司要做个与奥运相关的活动,扫车牌赢奖. 于是我就在上一个活动的基础上,将代码重新封装一下,并且将计算方式写的更 ...

  6. 移动端 H5图片裁剪插件,内置简单手势操作

    前面曾经写过一篇<H5图片裁剪升级版>,但里面需要借助第三方手势库,这次就不需要使用手势库,全部封装在代码中. 下图是裁剪的展示,下面就做了拖放和裁剪,没有做缩放,在插件中需要用到大量的计 ...

  7. apiCloud中图片裁剪模块FNImageClip的使用

    思路 1.获取需裁剪图片的地址 2.跳转到裁剪页面 3.裁剪成功返回新图片地址 4.替换原有图片地址 增加修饰和事件 str += '<li class="tu image" ...

  8. 微信中web页面实现和公众号中查看图片一样的效果

    最近开发了一套资讯相关的web页面,嵌套在微信中,可支持点赞.评论等...在文章详情中,图片需要点击放大,随手势放大缩小,左右可滑动切换,总之类似于微信公众号效果. 开始想的方案是用轮播插件.或者在i ...

  9. H5移动端图片裁剪(base64)

    在移动端开发的过程中,或许会遇到对图片裁剪的问题.当然遇到问题问题,不管你想什么方法都是要进行解决的,哪怕是丑点,难看点,都得去解决掉. 图片裁剪的jquery插件有很多,我也测试过很多,不过大多数都 ...

随机推荐

  1. mybatis 详解(三)------入门实例(基于注解)

    1.创建MySQL数据库:mybatisDemo和表:user 详情参考:mybatis 详解(二)------入门实例(基于XML) 一致 2.建立一个Java工程,并导入相应的jar包,具体目录如 ...

  2. 虚拟机下克隆3个centos系统并配置IP访问网络(转载)

    此文是保证linux系统能够上网 先查看本机的IP 打开虚拟机,更改虚拟机为桥接方式 在第一个虚拟机上打开终端,在命令行输入setup,选择NETWORK CONFIGRATION 回车后, 配置IP ...

  3. NOIP 2014 提高组 题解

    NOIP 2014 提高组 题解 No 1. 生活大爆炸版石头剪刀布 http://www.luogu.org/problem/show?pid=1328 这是道大水题,我都在想怎么会有人错了,没算法 ...

  4. 为什么是Spring Boot

    原文:https://dzone.com/articles/why-springboot 作者:Siva Prasad Reddy Katamreddy 译者:Oopsguy 本文介绍将各种Sprin ...

  5. oracle启动 init.ora spfile pfile[转]

    昨天晚上快下班的时候,公司数据库突然堵住了,一个buf表中累计了20多W的数据提取不出来,改了程序,效果不明显.因为之前有一次也重启过oracle,所以这次还是想把oracle重启一下,因为那些数据都 ...

  6. 关于浏览器解析html全过程详解

    本人web前端菜鸟一枚,第一次在这里发博客梳理知识,知识都是从各地方查阅引用以及自己的理解得来,有什么错误的地方欢迎指正. DOM文档通常加载的步骤: 1.解析HTML结构. 2.加载外部脚本和样式表 ...

  7. 外部排序&多路归并排序

    外部排序: 一.定义问题 外部排序指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序 整个文件的目的.外部排序最常 ...

  8. Elastic Stack

    Elastic Stack 开发人员不能登陆线上服务器查看详细日志 各个系统都有日志,日志数据分散难以查找 日志数据量大,查询速度慢,或者数据不够实时 官网地址:https://www.elastic ...

  9. ubuntu下发布asp.net core并用nginx代理之旅(续)

    前面实现了ubuntu下的发布,然而实际项目一般为visual studio中发布文件系统,然后上传至生产环境中,(部分参考:上传文件到linux - ubuntu) 这节就发布到生产环境中的: 1. ...

  10. 微信小程序源码推荐

    wx-gesture-lock  微信小程序的手势密码 WXCustomSwitch 微信小程序自定义 Switch 组件模板 WeixinAppBdNovel 微信小程序demo:百度小说搜索 sh ...