人人网首页拖拽上传详解(HTML5 Drag&Drop、FileReader API、formdata)

标签: HTML5 Javascript 我的作品 Ajax 前端开发 | 发表时间:2011-12-11 23:30 | 作者:彬Go
出处:http://blog.bingo929.com

  早在公元2011年6月3日傍晚,人人网推出了一个很装B且完全无视IE浏览器的功能——拖拽上床。哦,Sorry, 是 拖拽上传。到现在,这个功能已经上线了整整半年,这篇文章也足足拖延了半年才分享给大家,实在是对不住了,呵呵,今后彬Go一定要勤奋发Blog!

您还可以参考以下HTML5相关文章:
HTML5 Drag&Drop 拖拽、FileReader实例教程
HTML5 WebSockets 基础使用教程
关于HTML 5 canvas 的基础教程
让所有IE支持HTML5的解决方案
一起感受HTML5和CSS3的能量

  关于这个拖拽上传,其实国外有很多网站已经有这样的应用,最早推出拖拽上传应用的是Gmail,它支持标准浏览器下拖拽本地文件到浏览器中作为邮件的附件发送。人人网的这个拖拽上传也是同理,可以让使用标准浏览器的用户通过简单的拖拽行为,将本地文件夹中的照片直接上传到人人网,用户体验能得到提升的同时,也希望借此机会推广一下标准浏览器,淘汰IE。人人网当时也向广大用户推出升级浏览器活动,并喊出口号:”工欲善其计算机,必先利其浏览器”。本次拖拽上传的宣传口号:你敢”脱”,我就敢上传…

人人网 - 拖拽上传

  言归正题,首先看看效果,大家如果有 人人网帐号的话可以在首页试一试拖拽上传功能,下面是演示视频:

拖拽上传应用主要使用了以下 HTML5技术:

  • Drag&Drop : HTML5基于拖拽的事件机制.
  • File API :  可以很方便的让Web应用访问文件对象,File API包括 FileListBlobFileFileReaderURI scheme,本文主要讲解拖拽上传中用到的FileList和FileReader接口。
  • FormData : FormData是基于 XMLHttpRequest Level 2的新接口,可以方便web应用模拟Form表单数据,最重要的是它支持文件的二进制流数据,这样我们就能够通过它来实现AJAX向后端发送文件数据了。

HTML5 Drag&Drop 拖拽事件

  关于Drag&Drop拖拽事件,之前我写过一篇专门介绍的文章 《给力的 Google HTML5 训练营(HTML5 Drag&Drop 拖拽、FileReader实例教程)》,那篇文章详细讲解了Drag & Drap事件的原理和代码实例,这里的拖拽上传实现原理基本上是一样的,大家有兴趣或不太了解的话可以先看看那篇文章,我在这里就不再过多啰嗦了~下面直接出拖拽上传简要代码实例:

var oDragWrap = document.body;

//拖进
oDragWrap.addEventListener('dragenter', function(e) {
 e.preventDefault();
}, false);

//拖离
oDragWrap.addEventListener('dragleave', function(e) {
 dragleaveHandler(e);
}, false);

//拖来拖去 , 一定要注意dragover事件一定要清除默认事件
//不然会无法触发后面的drop事件
oDragWrap.addEventListener('dragover', function(e) {
 e.preventDefault();
}, false);

//扔
oDragWrap.addEventListener('drop', function(e) {
 dropHandler(e);
}, false);

var dropHandler = function(e) {
//将本地图片拖拽到页面中后要进行的处理都在这
}

获取文件数据 HTML5 File API

  在之前 那篇文章中我也有介绍过关于File API中的FileReader接口,作为  File API 的一部分, FileReader 专门用于读取文件,根据 W3C 的定义,FileReader 接口 “提供一些读取文件的方法与一个包含读取结果的事件模型”。关于FileReader的详细介绍和代码实例大家可以先去看看那篇 文章

  今天我着重介绍一下File API中的FileList接口,它主要通过两个途径获取本地文件列表,一是 <input type=”file”>的表单形式,另一种则是 e.dataTransfer.files拖拽事件传递的文件信息。很显然,我们这里会用到后者。

var fileList = e.dataTransfer.files;

  使用 files方法将会获取到拖拽文件的数组形势的数据,每个文件占用一个数组的索引,如果该索引不存在文件数据,将返回null值。可以通过 length属性获取文件数量.

var fileNum = fileList.length;

  拖拽上传需要注意的是需要判断两个条件,1:拖拽的是文件不是页面中的元素; 2:拖拽的是图片而不是其它文件,可以通过file.type属性获取文件的类型

//检测是否是拖拽文件到页面的操作
if (fileList.length === 0) {return;};
//检测文件是不是图片
if (fileList[0].type.indexOf('image') === -1) {return;}

  下面让我们来看看如何结合之前的拖拽事件来实现拖拽图片并在页面中进行预览:

var dropHandler = function(e) {
 e.preventDefault();

 //获取文件列表
 var fileList = e.dataTransfer.files;

 //检测是否是拖拽文件到页面的操作
 if (fileList.length == 0) {return;};

 //检测文件是不是图片
 if (fileList[0].type.indexOf('image') === -1) {return;}

 //实例化file reader对象
 var reader = new FileReader();
 var img = document.createElement('img');

 reader.onload = function(e) {
  img.src = this.result;
  oDragWrap.appendChild(img);
 }
 reader.readAsDataURL(fileList[0]);

}

  这里有一个简单的 拖拽图片预览的Demo

  这时你如果用FireBug等类似调试工具查看DOM的话,会看到<img>标签的src属性是一个超长的文件二进制数据,所以如果DOM有很多这类图片,那就要当心浏览器性能了,因为这些数据极大地扩充的页面的代码量,而每次页面的reflow都会对浏览器形成很大的负担,So,如果这些图片还在DOM中,那就尽量不要做动画或任何重绘操作,如果真的要做就尽量让图片脱离文档流,让其绝对定位比较靠谱。

补充:可以使用 window.URL.createObjectURL(file)来获取文件的URL(Chrome下用 window.webkitURL.createObjectURL(file)),这种方式获取的URL要比上面说的 readAsDataURL简短很多。而且可以省去使用FileReader。下面是使用readAsDataURL与createObjectURL生成的<img>代码对比:

readasdataurl-vs-createobjecturl

优化后的代码:

var dropHandler = function(e) {
 e.preventDefault();

 var fileList = e.dataTransfer.files;  //获取文件列表
 var img = document.createElement('img');

 //检测是否是拖拽文件到页面的操作
 if (fileList.length == 0) {return;};

 //检测文件是不是图片
 if (fileList[0].type.indexOf('image') === -1) {return;}
 
 if (window.URL.createObjectURL) {
  //FF4+
  img.src = window.URL.createObjectURL(fileList[0]);
 } else if (window.webkitURL.createObjectURL) {
  //Chrome8+
  img.src = window.webkitURL.createObjectURL(fileList[0]);
 } else {
  //实例化file reader对象
  var reader = new FileReader();

  reader.onload = function(e) {
   img.src = this.result;
   oDragWrap.appendChild(img);
  }
  reader.readAsDataURL(fileList[0]);
 }
}

  需要注意的是, window.URL.createObjectURL是有生命周期的,也就意味着你每用此方法获取URL,其生命周期都会和DOM一样,它会单独占用内存,所以当删除图片或不再需要它是,记得用 window.URL.revokeObjectURL(file)来释放其内存。当然,如果你没有释放,刷新页面也是可以释放的。

AJAX上传图片(file.getAsBinary & FormData)

  既然已经获取到了拖拽到web页面中图片的数据,下一步就是将其发送到服务器端了。

  话说HTML5时代之前,AJAX传输文件二进制流数据是不可能完成的事情,而现在我们完全可以通过 file.getAsBinary获取文件的二进制数据流,进而将其当做XHR的data数据传送到后端,8过由于Chrome不支持file的getAsBinary方法,FF3.6+支持此方法。所以Chrome就要另寻它法了,这时我们发现 XMLHttpRequest Level 2中的FormData接口完美解决了这个问题,它可以很快捷的模拟Form表单数据并通过AJAX发送至后端,FormData的支持情况是FF5及以上支持,Chrome12及以上支持。

   file.getAsBinary获取文件流很简单,但是要想上传数据,就要模拟一下表单的数据格式了,首先看看模拟表单的js代码, FormData模拟表单数据时更是简洁,不用麻烦的去拼字符串,而是直接将数据append到formdata对象中即可:

var xhr = new XMLHttpRequest();
var url = 'http://upload.renren.com/......';
var boundary = '-----------------------' + new Date().getTime();
var fileName = file.name;

xhr.open("post", url, true);
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);

if (window.FormData) {
 //Chrome12+
 var formData = new FormData();
 formData.append('file', file);
 formData.append('hostid', userId);
 formData.append('requestToken', t);

 data = formData;
} else if (file.getAsBinary) {
 //FireFox 3.6+
 data = "--" +
 boundary +
 crlf +
 "Content-Disposition: form-data; " +
 "name=\"" +
 'file' +
 "\"; " +
 "filename=\"" +
 unescape(encodeURIComponent(file.name)) +
 "\"" +
 crlf +
 "Content-Type: image/jpeg" +
 crlf +
 crlf +
 file.getAsBinary() +
 crlf +
 "--" +
 boundary +
 crlf +
 "Content-Disposition: form-data; " +
 "name=\"hostid\"" +
 crlf +
 crlf +
 userId +
 crlf +
 "--" +
 boundary +
 crlf +
 "Content-Disposition: form-data; " +
 "name=\"requestToken\"" +
 crlf +
 crlf +
 t +
 crlf +
 "--" +
 boundary +
 '--';
}

xhr.send(data);

首先表单数据headers头信息需要以下两项:

  • Content-Type : 设置其为multipart/form-data来模拟表单数据
  • boundary : 表单数据中的分隔符,用于分隔不同的文件或表单项,这是服务器端设置的格式。

发送时的post数据类似这样:

-------------------------1323611763556
Content-Disposition: form-data; name="file"; filename="4.jpg"
Content-Type: image/jpeg

ÿØÿà�JFIF�...这里是文件二进制流...~iúoî­5P%-vãîHü 4QHgÿÙ
-------------------------1323611763556
Content-Disposition: form-data; name="hostid"

229421603
-----------------------------1323612996486

Content-Disposition: form-data; name="requestToken"

369009193
-------------------------1323611763556--

好了,现在文件上传成功后你就可以按照平常AJAX的操作来进行后续处理了。

最后,再来总结一下拖拽上传的技术要点:

  1. 监听拖拽:监听页面元素的拖拽事件,包括: dragenter、dragover、dragleave和drop,一定要将dragover的默认事件取消掉,不然无法触发drop事件。如需拖拽页面里的元素,需要给其添加属性draggable=”true”;
  2. 获取拖拽文件:在drop事件触发后通过 e.dataTransfer.files获取拖拽文件列表, .length属性获取文件数量,.type属性获取文件类型。
  3. 读取图片数据并添加预览图:实例化 FileReader对象,通过其 readAsDataURL(file)方法获取文件二进制流,并监听其 onload事件,将 e.result赋值给img的src属性,最后将图片append到DOM中。
  4. 发送图片数据:使用 file.getAsBinaryFormData分别模拟表单数据AJAX提交文件流。

  OK,拖拽上传就讲到这里,欢迎大家一起探讨。也在这里推荐一下 人人网FED的Blog,大家多多捧场,哈~

留意更新

订阅彬Go以查看HTML5最新资源、教程。

转载声明:
原载: 彬Go——关注前端开发 & Web UI设计
本文链接: http://blog.bingo929.com/renren-drag-drop-photo-filereader-formdata.html
如需转载必须以链接形式注明原载或原文地址,谢谢合作

相关 [人人网 首页 上传] 推荐:

人人网首页拖拽上传详解(HTML5 Drag&Drop、FileReader API、formdata)

- - 彬Go
  早在公元2011年6月3日傍晚,人人网推出了一个很装B且完全无视IE浏览器的功能——拖拽上床. 哦,Sorry, 是 拖拽上传. 到现在,这个功能已经上线了整整半年,这篇文章也足足拖延了半年才分享给大家,实在是对不住了,呵呵,今后彬Go一定要勤奋发Blog. 您还可以参考以下HTML5相关文章:.

网站首页的设计

- - 标点符
网站首页的设计是一件非常让人头痛的事. 虽然她看上去很简单:产品经路随便从网站里拿点东西出来,堆出一个看上去靠谱的页面. 也正因此,它往往非常麻烦:很多人都可以发表自己的见解,以致最终陷入到无尽的争执中. 首页的设计相对于其他页面要难的原因是,其他页面更多的是解决用户“能做”的问题,而首页的更多要解决用户“想做”的问题.

SpringMVC+ajaxfileupload上传

- - CSDN博客互联网推荐文章
看这篇的文章之前最好看一下上篇文章这样可以更好的理解. 整个项目的基本配置和上面差不多. 不同的是在webRoot文件夹下的js中引入jQuery.js 和ajaxfileupload.js. 如何没有这个两个js文件可以到各自的官网下载. DemoController.java   跳转到upload.jsp.

多文件上传

- - BlogJava-首页技术区
多文件上传 jquery的插件. 使用的方法  导入 jquery.js 及 jquery.MultiFile.js ,. 方式一: 后台是文件数组  .  private File[] upload; // 与jsp表单中的名称对应. 在 form 中加入 即可.

北邮网站首页被黑

- andi - Solidot
wmr 写道 "“北邮人论坛”的一个帖子的截图显示,网站首页被替换成一个“GFW不灭明年我还会来”之后是一些敌对势力网站的名称. 最嚣张的是大字“immmmm.com求GFW. ”稍有常识的人都知道,假如我们的人民公仆轻轻一碰指尖,这个螳臂当车歹徒的网站不就被封了吗. 然而该网站上并没有违法内容,所以也没有封,这只能说明,我们的网络管理,是完全依法的.

首页设计的可用性和PET

- BEAsThAnG - 所有文章 - UCD大社区
网站的首页是一个让人头疼的东西. 有时它看起来很简单:首页就是网站内容的整合,一个产品经理随便从网站里拿点东西出来,就能堆出一个看上去靠谱的首页. 也正因此,它往往非常麻烦:很多人都可以发表自己的见解,而这时交互设计师的一些手段(比如流程图、概念图等),在面对首页设计时也难派上用场,以致最终陷入到无尽的争执中.

2011彩票首页开发实践

- 红茶 - Taobao UED Team
彩票新版首页已经悄无声息的上线一个月时间,目前只在老版首页中挂了一个链接入口,没有覆盖之前的版本,这种新旧版并行的措施,一方面为了给用户提供足够的缓冲,另外一方面则是收集用户的反馈意见来完善我们的产品,使之更加贴近用户. 此次彩票首页的改版,采用语义化的html5标签来布局页面,针对不支持html5的IE系列浏览器(IE8及其以下版本),依然采用js方法激活自定义标签即可.

Google 首页悼念 Steve Jobs 逝世

- Yousri - 谷奥——探寻谷歌的奥秘
今天全球所有地区的Google主页都挂上了 “Steve Jobs, 1955 - 2011” 以悼念Steve Jobs的辞世. Steve Jobs并未链接至Google搜索结果,而是apple.com主页. 史蒂芬·保罗·乔布斯(英语:Steven Paul Jobs,1955年2月24日-2011年10月5日),简称为史蒂夫·乔布斯(英语:Steve Jobs),是苹果公司的现任董事会主席,前任首席运行官及创办人之一,同时也是前皮克斯动画工作室的董事长及首席执行官(皮克斯动画工作室已于2006年被迪士尼收购[2]).

浅析网页界面设计-首页

- 多二度 - 所有文章 - UCD大社区
开宗明义,无论是对于一篇文章、一场会议或一部专题片,还是对于一个网站 来说,都是必不可少的. 那么能为一个网站开宗明义的地方(标签)就是Title(标题)和Description(描述、副标题),而能够为Title和 Description提供进一步诠释的就是网站的首页. 页面的重要性是建立在它所呈现信息的基础之上,反过来说,页面要向用户展示哪些信息是决定其重要与否的首要指标.

网站首页点击分布分析

- - 蓝鲸的网站分析笔记
首页,是网站中最重要的一个页面,通常也是被浏览次数最多的页面. 我相信在大部分网站的Google Analytics内容报告中,首页在 综合浏览量指标中都是排在第一位的. 同时,首页也是网站中最特殊的一个页面,他相当于整个网站的一个目录,为访问者提供了网站中最有价值的内容简介和入口链接,通过首页访问者可以到达网站的大部分区域.