1、前端拖拽图片
之前有篇文章说到HTML5的拖拽(drag
、drop
,详见:/post/jquery-plugin-1-jquery-drag-and-html5-draggable-api-and-compatibility.html)。这里说的拖拽图片只是弱化了拖的概念,而强化了拽的操作。从浏览器外部拖动一个文件到浏览器中来,如:
拖动文件到浏览器之后,就会打开浏览器支持的文件,如常用的txt、图片等,如下:
操作如上常用文件,一般都有默认行为来处理这样的事件。这里说的拖拽上传也是一个道理,就是要做的是从浏览器外部拖动文件到浏览器中来,并且在当前页面上传该文件。在表面上看来,这个方法很适合用户体验,但实际情况是用户体验急剧下降。因为大多数人浏览网页的时候是全最大化窗口查看的,而如果要拖拽上传文件的话,那么就需要把浏览器窗口后,非常的麻烦。就比如wordpress的添加媒体功能,也是支持拖拽上传图片的,可用过的人又有多少呢?
倒还不如点击选择图片操作来的更加快捷,不过本文旨在说明问题。也许有更好玩的例子没有被发现而已,在此只做抛砖引玉。
1.1、拖拽文件的files对象
在拖拽文件到浏览器,我们需要处理这些文件。
$.fn.listen=function(type,fn){ returnthis.each(function(){ $(this)[0].addEventListener(type,function(e){ if(!fn.call($(this),e)){ e.stopPropagation(); e.preventDefault(); } },0); }); } $(document).bind("dragenter",function(){ returnfalse; }).bind("dragover",function(){ returnfalse; }).listen("drop",function(e){ // 这里处理拖拽进来的文件(们) });
如上,可能细心的朋友发现了一些不同。那就是document使用了两个方法来绑定事件,dragenter
、dragover
使用jquery的bind
方法绑定,而drop事件却使用自定义的listen
方法绑定,这是为什么呢?因为jquery的事件绑定,为了兼容操作处理的事件的参数event,删减了一些属性使其在各个浏览器都保持一致。而此处用的是event的HTML5属性,恰巧是不兼容低级浏览器的,所以使用jquery的bind
方法是无法获取的。我们可以来验证一下这个说法:
$(document).bind("dragenter",function(){ returnfalse; }).bind("dragover",function(){ returnfalse; }).bind("drop",function(e){ console.log(e); }).listen("drop",function(e){ console.log(e); });
拖拽文件到这个页面,在浏览器控制台得到两个不尽相同的对象:
如图示黄色背景的dataTransfer
是浏览器默认的event所独有的,而我们此处用的files
对象这是在这里面。
console.log(e.dataTransfer.files);
在浏览器控制台输出了:
由图示可知,该files对象是一个数组。值得注意的是,该files对象只读。可以接收到文件的基本信息(最后修改时间、文件名称、大小、类型),我们就可以在客户端来处理上传文件之前的操作。比如限制文件必须为图片格式、文件的大小不能超过100KB。
$(document).bind("dragenter",function(){ returnfalse; }).bind("dragover",function(){ returnfalse; }).listen("drop",function(e){ var files=e.dataTransfer.files; var error=''; for(var i=0;i<files.length;i++){ console.log(files[i]); if(files[i].size>1024*100)error+='第'+(i+1)+'个文件超过大小限制\n'; if(!/image/i.test(files[i].type))error+='第'+(i+1)+'个文件不是图片格式\n'; } if(error)alert(error); });
会弹出如下警告:
1.2、处理文件的FileReader对象
用户拖拽了符合我们预期的文件,我们就需要对其进行操作。比如本文要做的是用户必须上传100KB以内的图片类型文件。在获取到files对象之后,我们要使用FileReader
对象来读取file的文件(该对象人如其名)。
$(document).bind("dragenter",function(){ returnfalse; }).bind("dragover",function(){ returnfalse; }).listen("drop",function(e){ var files=e.dataTransfer.files; var error=''; for(var i=0;i<files.length;i++){ if(files[i].size>1024*100)error+='第'+(i+1)+'个文件超过大小限制\n'; if(!/image/i.test(files[i].type))error+='第'+(i+1)+'个文件不是图片格式\n'; } // 有错误抛弃 if(error){ alert(error); return; } // 处理文件队列 for(var i=0;i<files.length;i++){ read(files[i]); } }); function read(file){ var reader=newFileReader(); reader.onload=function(e){ $("body").append('<img src="'+e.target.result+'" alt="" />'); } reader.readAsDataURL(file); }
在拖拽合适大小的图片文件到该页面之后:
FileReader
对象是干嘛的呢?它可以读取文件,并进行相应的操作,涉及到安全问题,该操作不会对图片的源文件产生影响。该对象有如下属性和方法以及事件:
- 属性:为空
- 方法:
- about:中断文件读取
- readAsBinaryString:读取文件为二进制
- readAsDataURL:读取文件dataUrl(本例用到的)
- readAsText:读取文件为文本
- 事件:
- onabort:读取文件中断时触发
- onerror:读取文件出错时触发
- onload:读取文件成功时触发(本例用到的)
- onloadstart:读取文件开始时触发
- onprogress:读取文件中时一直触发
- onloadend:读取文件结束时触发(成功和失败都会触发,如
jquery.ajax
的complete
)
在监听到文件读取成功时,该事件参数e有e.target.result
指向的是读取的结果(即本例描述的文件的二进制url)。
1.3、处理表单的FormData对象
选到了合适大小的图片文件,我们就需要把这些文件无刷新上传到服务器,但我们如何发送这些图片到后端呢?传统的ajax方法一直是发送文本格式(Content-Type:application/x-www-form-urlencoded)而发送图片这些大文件需要用到浏览器的原生表单(Content-Type:multipart/form-data)。所幸的是,HTML5的支持的FromData
属性可以帮忙完成这一个过程,即封装图片成表单数据以用于提交到后端。
FormData
类似于jquery.serialize
,jquery这一次又走在了前列。不过FormData
比serialize
要强大一点,它不仅可以在提交表单数据里增加纯文本也可以增加图片等二进制数据。其基本使用方法是:
- var form1=newFormData();
- form1.append("name1","value1");
- form1.append("name2","value2");
- form1.append("file",file对象);
如上,其作用相当于:
- <form>
- <inputtype="text"name="name1"value="value1">
- <inputtype="text"name="name2"value="value2">
- <inputtype="file"name="file"id="">
- </form>
所以接着1.2继续,把合适的图片组装成form数据便于异步传输。
- $(document).bind("dragenter",function(){
- returnfalse;
- }).bind("dragover",function(){
- returnfalse;
- }).listen("drop",function(e){
- var files=e.dataTransfer.files;
- var error='';
- for(var i=0;i<files.length;i++){
- if(files[i].size>1024*100)error+='第'+(i+1)+'个文件超过大小限制\n';
- if(!/image/i.test(files[i].type))error+='第'+(i+1)+'个文件不是图片格式\n';
- }
- // 有错误抛弃
- if(error){
- alert(error);
- return;
- }
- // 处理文件队列
- for(var i=0;i<files.length;i++){
- read(files[i]);
- }
- // 组装成表单数据
- var formData=newFormData();
- for(var i=0;i<files.length;i++){
- formData.append('files[]',files[i]);
- }
- });
- // 读取图片
- function read(file){
- var reader=newFileReader();
- reader.onload=function(e){
- $("body").append('<img src="'+e.target.result+'" alt="" />');
- }
- reader.readAsDataURL(file);
- }
注:以上代码仅供示例,上述的formData.append('files[]',...)
,有中括号表示多文件批量上传。
1.4、ajax上传的XMLHttpRequest对象
有了表单数据,我们就可以使用异步传输发送给服务端。
- $(document).bind("dragenter",function(){
- returnfalse;
- }).bind("dragover",function(){
- returnfalse;
- }).listen("drop",function(e){
- var files=e.dataTransfer.files;
- var error='';
- for(var i=0;i<files.length;i++){
- if(files[i].size>1024*100)error+='第'+(i+1)+'个文件超过大小限制\n';
- if(!/image/i.test(files[i].type))error+='第'+(i+1)+'个文件不是图片格式\n';
- }
- // 有错误抛弃
- if(error){
- alert(error);
- return;
- }
- // 处理文件队列
- for(var i=0;i<files.length;i++){
- read(files[i]);
- }
- // 组装成表单数据
- var formData=newFormData();
- for(var i=0;i<files.length;i++){
- formData.append('files[]',files[i]);
- }
- // 异步传输
- ajax();
- });
- // 读取图片
- function read(file){
- var reader=newFileReader();
- reader.onload=function(e){
- $("body").append('<img src="'+e.target.result+'" alt="" />');
- }
- reader.readAsDataURL(file);
- }
- // 异步传输
- function ajax(formData){
- var xhr=newXMLHttpRequest();
- xhr.open("post","upload.php");
- xhr.onload=function(){
- alert("上传完成!");
- }
- xhr.send(formData);
- }
如果上传不出错的话,那么在上传结束的时候会弹出“上传完成!”的确认框。
1.5、ajax上传的进度
因为上传文件和上传纯文本,其数据量不是一个级别的,所以为了友好的用户体验,我们需要实时告诉用户上传的进度目前是多少。可喜的是,HTML5已经帮我们完成这个步骤,可以这么做来实时获取当前上传的进度:
- // 异步传输
- function ajax(formData){
- var xhr=newXMLHttpRequest();
- xhr.open("post","upload.php");
- xhr.onload=function(){
- alert("上传完成!");
- }
- // 上传进度
- xhr.upload.onprogress=function(e){
- if(e.lengthComputable){
- var percent =(e.loaded / e.total *100|0)+"%";
- console.log(percent);
- }
- }
- xhr.send(formData);
- }
关于XMLHttpRequest
的更多使用方法,这里不做赘述,可以参考文章末尾的参考资料。
1.6、完整的源代码
- <!doctype html>
- <htmllang="en">
- <head>
- <metacharset="UTF-8">
- <title>拖拽图片到这里来并上传</title>
- <style>
- h2 small{
- color:#888;
- font-weight: normal;
- margin-left:30px;
- font-size:80%;
- }
- .box{
- border:1px solid #ccc;
- background:#f5f5f5;
- padding:10px;
- margin-bottom:20px;
- }
- .box2{
- border:1px solid #EB9D45;
- background:#FFF2E3;
- padding:10px;
- margin-bottom:20px;
- }
- #progress{
- margin-left:10px;
- }
- .box img,
- .box2 img{
- max-height:100px;
- height:auto;
- width:auto;
- padding:2px;
- border:1px solid #ddd;
- background:#fff;
- margin-right:20px;
- margin-bottom:20px;
- }
- </style>
- </head>
- <body>
- <divclass="box">
- <h2>拖拽图片到这里来<smallid="message"></small></h2>
- <divid="box"></div>
- </div>
- <p>原文:<ahref="/post/undefined.html700">/post/undefined.html700</a></p>
- <divclass="box2">
- <h2>已上传的图片<smallid="progress"></small></h2>
- <divid="box2"></div>
- </div>
- <scriptsrc="http://libs.baidu.com/jquery/2.0.3/jquery.min.js"></script>
- <script>
- $.fn.listen=function(type,fn){
- returnthis.each(function(){
- $(this)[0].addEventListener(type,function(e){
- if(!fn.call($(this),e)){
- e.stopPropagation();
- e.preventDefault();
- }
- },0);
- });
- }
- var $box=$("#box");
- var $message=$("#message");
- var $box2=$("#box2");
- var $progress=$("#progress");
- var isuploading=0;
- var hasBtSize=0;
- var errorArray=[];
- $(document).bind("dragenter",function(){
- returnfalse;
- }).bind("dragover",function(){
- returnfalse;
- }).listen("drop",function(e){
- if(isuploading)return;
- var error='';
- var num=1;
- var files=e.dataTransfer.files;
- var j=files.length;
- $box.empty();
- for(var i =0; i < j; i++){
- (function(i){
- var reader =newFileReader();
- reader.onload =function(event){
- $box.append("<img src='"+ event.target.result +"' /> ");
- if(files[i].size>1024*400||!/image/.test(files[i].type)){
- errorArray.push(num);
- }
- num++;
- if(num>j){
- if(errorArray.length){
- error=errorArray.join(',');
- $message.html("其中第 "+error+" 个文件大小超过限制或不是图片,本次上传已被取消。");
- errorArray=[];
- return;
- }
- upload(files);
- }
- };
- reader.readAsDataURL(files[i]);
- })(i);
- }
- returnfalse;
- });
- function upload(files){
- if(isuploading)return;
- isuploading=1;
- var formData=newFormData();
- var isComplete=0;
- for(var i=0; i <files.length; i++){
- formData.append("files[]",files[i]);
- };
- var xhr=newXMLHttpRequest();
- xhr.open('post','upload.php');
- xhr.onload=function(){
- $progress.html("上传完成!");
- };
- xhr.onreadystatechange=function(){
- if(xhr && xhr.readyState ===4){
- status = xhr.status;
- if(!isComplete && status >=200&& status <300|| status ===304){
- isComplete =true;
- var json=$.parseJSON(xhr.responseText);
- $box2.empty();
- $box.empty();
- $message.empty();
- $.each(json,function(key,val){
- $box2.append("<img src='"+ val +"' />");
- });
- isuploading=0;
- }elseif(!isComplete){
- isComplete =true;
- $box.html("网络错误!");
- isuploading=0;
- }
- xhr =null;
- }
- }
- xhr.upload.onprogress =function(event){
- if(event.lengthComputable){
- var percent =(event.loaded / event.total *100|0)+"%";
- $progress.html(percent);
- }
- }
- xhr.send(formData);
- }
- </script>
- </body>
- </html>
在上传过程中:
2、后台上传处理
- <?php
- $uploaddir ='img/';
- $src_array=array();
- if(isset($_FILES['files'])){
- foreach($_FILES['files']["error"]as $key => $error){
- if($error==UPLOAD_ERR_OK){
- if(!preg_match("#image#",$_FILES["files"]["type"][$key]))continue;
- if($_FILES["files"]["size"][$key]>1024*400)continue;
- $tmp_name = $_FILES["files"]["tmp_name"][$key];
- $name = $_FILES["files"]["name"][$key];
- $name= date("YmdHis",time()).preg_replace("#[^\w\.]#","",$name);
- $uploadfile = $uploaddir.$name;
- $ret=move_uploaded_file($tmp_name, $uploadfile);
- if($ret){
- $src_array[]=$uploadfile;
- }
- }
- }
- }
- echo json_encode($src_array);
3、demo
demo地址:http://demo.qianduanblog.com/2700/1.html
jquery批量上传插件详细见:/post/jquery-plugin-11-jquery-upload-free-refresh-batch-ajax-progress-upload.html。
4、参考资料
- http://www.zhangxinxu.com/wordpress/2013/10/understand-domstring-document-formdata-blob-file-arraybuffer/
- http://www.jsmix.com/blog/html5/file-reader.html
- https://developer.mozilla.org/en-US/docs/Web/API/FormData?redirectlocale=en-US&redirectslug=Web%2FAPI%2FXMLHttpRequest%2FFormData
- http://www.html5rocks.com/zh/tutorials/file/xhr2/
- https://developer.mozilla.org/en-US/docs/Web/API/XMLHTTPRequest
- http://developer.51cto.com/art/200911/164751.htm
- http://www.html5rocks.com/zh/tutorials/file/dndfiles/