谈谈基于iframe、FormData、FileReader三种无刷新上传文件的方法

发请求有两种方式,一种是用ajax,另一种是用form提交,默认的form提交如果不做处理的话,会使页面重定向。以一个简单的demo做说明:

html如下所示,请求的路径action为"upload",其它的不做任何处理:

 <form method="POST" action="upload" enctype="multipart/form-data">
  名字 <input type="text" name="user"></input>
  头像 <input type="file" name="file"></input>
  <input type="submit" id="_submit" value="提交"></input>
 </form> 

服务端(node)response直接返回: "Recieved form data",演示如下:

可以看到默认情况下,form请求upload的同时重定向到upload。但是很多情况下是希望form请求像ajax一样,不会重定向或者刷新页面。像上面的场景,当上传完成之后,将用户选择的头像显示在当前页面。

解决办法第一种是使用html5的FormData,将form里面的数据封装到FormData对象里,然后再以POST的方式send出去。如下面代码所示,对提交按钮的单击事件做一个响应,代码第6行获取到form的DOM对象,然后第8行构造一个FormData的实例,第18行,将form数据发送出去。

document.getElementById("_submit").onclick = function(event){
   //取消掉默认的form提交方式
   if(event.preventDefault) event.preventDefault();
   else event.returnValue = false;       //对于IE的取消方式
   var formDOM = document.getElementsByTagName("form")[];
   //将form的DOM对象当作FormData的构造函数
   var formData = new FormData(formDOM);
   var req = new XMLHttpRequest();
   req.open("POST", "upload");
   //请求完成
   req.onload = function(){
    if(this.status === ){
      //对请求成功的处理
    }
   }
   //将form数据发送出去
   req.send(formData);
       //避免内存泄漏
       req = null;
 } 

上传成功后,服务将返回图片的访问地址,补充14行对请求成功的处理:在submit按钮的上方位置显示上传的图片:

var img = document.createElement("img");
     img.src = JSON.parse(this.responseText).path;
     formDOM.insertBefore(img, document.getElementById("_submit")); 

示例:

如果使用jQuery,可以把formData作为ajax的data参数,同时设置contentType: false和processData: false,告诉jQuery不要去处理请求头和发送的数据。

看起来这种提交方式跟ajax一样,但是其实并不是完全一样,form提交的数据格式有三种,如果要上传文件则必须为multipart/form-data,所以上面的form提交请求里的http的头信息里面的Content-Type为multipart/form-data,而普通的ajax提交为application/json。form提交完整的Content-Type如下:

"content-type":"multipart/form-data; boundary=------WebKitFormBoundaryYOE7pWLqdFYSeBFj"

除了multipart/form-data之外,还指定了boundary,这个boundary的作用是用来区分不同的字段。由于FormData对象是不透明的,调用JSON.stringify将会返回一个空的对象{},同时FormData只提供append方法,所以无法得到FormData实际上传的内容,但是可以通过分析工具或者服务收到的数据进行查看。在上面如果上传一个文本文件,那么服务收到的POST数据的原始格式是这样的:

------WebKitFormBoundaryYOE7pWLqdFYSeBFj

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

abc

------WebKitFormBoundaryYOE7pWLqdFYSeBFj

Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

这是一个文本文件的内容。

------WebKitFormBoundaryYOE7pWLqdFYSeBFj--

从上面服务收到的数据看出FormData提交的格式,每个字段以boundary隔开,最后以--结束。而ajax请求,send出去的数据格式是自定义的,一般都是以key=value中间用&连接:

 var req = new XMLHttpRequest();
  var sendData = "user=abc&file=这是一个文本文件的内内容";
  req.open("POST", "upload");
  //发送的数据需要转义,见上面提到的三种格式
  req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  req.send(sendData); 

服务就会收到和send发出去的字符串一模一样的内容,然后再作参数解析,所以就得统一参数的格式:

user=abc&file=这是一个文本文件的内容

从这里可以看出POST本质上并不比GET安全,POST只是没有将数据放在网址传送而已。

考虑到FormData到了IE10才支持,如果要支持较低版本的IE,那么可以借助iframe。

文中一开始就说,默认的form提交会使页面重定向,而重定向的规则在target中指定,可以和a标签一样指定为"_blank",在新窗口中打开;还可以指定为一个iframe,在该iframe中打开。所以可以弄一个隐藏的iframe,将form的target指向这个iframe,当form请求完成时,返回的数据就会由这个iframe显示,正如上面在新页面显示的:"Recieved form data"。请求完成后,iframe加载完成,触发load事件,在load事件的处理函数里,获取该iframe的内容,从而拿到服务返回的数据了!拿到后再把iframe删掉。

在提交按钮的响应函数里,首先创建一个iframe,设置iframe为不可见,然后再添加到文档里:

var iframe = document.createElement("iframe");
  iframe.width = 0;
  iframe.height = 0;
  iframe.border = 0;
  iframe.name = "form-iframe";
  iframe.id = "form-iframe";
  iframe.setAttribute("style", "width:0;height:0;border:none");
  //放到document
  this.form.appendChild(iframe); 

改变form的target为iframe的name值:

 this.form.target = "form-iframe"; 

然后再响应iframe的load事件:

 iframe.onload = function(){
   var img = document.createElement("img");
   //获取iframe的内容,即服务返回的数据
   var responseData = this.contentDocument.body.textContent || this.contentWindow.document.body.textContent;
   img.src = JSON.parse(responseData).path;
   f.insertBefore(img, document.getElementById("_submit"));
   //删掉iframe
   setTimeout(function(){
    var _frame = document.getElementById("form-iframe");
    _frame.parentNode.removeChild(_frame);
   }, 100);
   //如果提示submit函数不存在,请注意form里面是否有id/value为submit的控件
   this.form.submit();
  } 

第二种办法到这里就基本可以了,但是如果看163邮箱或者QQ邮箱上传文件的方式,会发现和上面的两种方法都不太一样。用httpfox抓取请求的数据,会发现上传的内容的格式并不是上面说的用boundary隔开,而是直接把文件的内容POST出去了,而文件名、文件大小等相关信息放在了文件的头部。如163邮箱:

POST Data:

this is a text

Headers:

Mail-Upload-name: content.txt
    Mail-Upload-size: 15

可以推测它们应该是直接读取了input文件的内容,然后直接POST出去了。要实现这样的功能,可以借助FileReader,读取input文件的内容,再保留二进制的格式发送出去:

 var req = new XMLHttpRequest();
   req.open("POST", "upload");
   //设置和邮箱一样的Content-Type
   req.setRequestHeader("Content-Type", "application/octet-stream");
   var fr = new FileReader();
   fr.onload = function(){
    req.sendAsBinary(this.result);
   }
   req.onload = function(){
     //一样,省略
   }
    //读取input文件内容,放到fileReader的result字段里
   fr.readAsBinaryString(this.form["file"].files[0]); 

代码第13行执行读文件,读取完毕后触发第6行的load响应函数,第7行以二进制文本形式发送出去。由于sendAsBinary的支持性不是很好,可以自行实现一个:

 if(typeof XMLHttpRequest.prototype.sendAsBinary === 'undefined'){
  XMLHttpRequest.prototype.sendAsBinary = function(text){
  var data = new ArrayBuffer(text.length);
  var uia = new UintArray(data, );
  for (var i = ; i < text.length; i++){
   uia[i] = (text.charCodeAt(i) & xff);
  }
  this.send(uia);
  }
 } 

代码的关键在于第6行,将字符串转成8位无符号整型,还原二进制文件的内容。在执行了fr.readAsBinaryString之后,二进制文件的内容将会以utf-8的编码以字符串形式存放到result,上面的第6行代码将每个unicode编码转成整型(&0xff或者parseInt),存放到一个8位无符号整型数组里面,第8行把这个数组发送出去。如果直接send,而不是sendAsBinary,服务收到的数据将无法正常还原成原本的文件。

上面的实现需要考虑文件太大,需分段上传的问题。

关于FileReader的支持性,IE10以上支持,IE9有另外一套File API。

文章讨论了3种办法实现无刷新上传文件,分别是使用iframe、FormData和FileReader,支持性最好是的iframe,但是从体验的效果来看FormData和FileReader更好,因为这两者不用生成一个无用的DOM再删除,其中FormData最简单,而FileReader更加灵活。

下面给大家介绍iframe无刷新上传文件

form.html
<form enctype="multipart/form-data" method="post" target="upload" action="upload.php" >
<input type="file" name="uploadfile" />
<input type="submit" />
</form>
<iframe name="upload" style="display:none"></iframe> 

<!--和一般的<form>标签相比多了一个target属性罢了,用于指定标签页在哪里打开以及提交数据。

如果没有设置该属性,就会像平常一样在本页重定向打开action中的url。

而如果设置为iframe的name值,即"upload"的话,就会在该iframe内打开,因为CSS设置为隐藏,因而不会有任何动静。若将display:none去掉,还会看到服务器的返回信息。

--> 

upload.php
<?php
header("Content-type:text/html;charset=utf-");
class upload{
 public $_file;
 public function __construct(){
  if(!isset($_FILES['uploadfile'])){
   $name=key($_FILES);
  }
  if(!isset($_FILES['uploadfile'])){
   throw new Exception("并没有文件上传");
  }
  $this->_file=$_FILES['uploadfile']; //$this->_file一维数组
  var_dump($this->_file);
  //判断文件是否是通过 HTTP POST 上传的
  //如果 filename 所给出的文件是通过 HTTP POST 上传的则返回 TRUE。这可以用来确保恶意的用户无法欺骗脚本去访问本不能访问的文件,例如 /etc/passwd。
  if(!is_uploaded_file($this->_file['tmp_name']))
   throw new Exception("异常情况");
  if($this->_file['error'] !== )
   throw new Exception("错误代码:".$this->_file['error']);
 }
 public function moveTo($new_dir){
  $real_dir=$this->checkDir($new_dir).'/';
  $real_dir=str_replace("\\","/",$real_dir);
  if(!move_uploaded_file($this->_file['tmp_name'],$real_dir.$this->_file['name'])){
   exit('上传失败');
  }
  echo "<script type='text/javascript'>alert('上传成功')</script>";
 }
 public function checkDir($dir){
  if(!file_exists($dir)){
   mkdir($dir,,true);
  }
  return realpath($dir);
 }
}
$upload=new upload();
$new_dir="./a/b";
$upload->moveTo($new_dir);
(0)

相关推荐

  • JavaSctit 利用FileReader和滤镜上传图片预览功能

    FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File或 Blob对象指定要读取的文件或数据. 1.FileReader接口的方法 FileReader接口有4个方法,其中3个用来读取文件,另一个用来中断读取.无论读取成功或失败,方法并不会返回读取结果,这一结果存储在result属性中. 2.FileReader接口事件 FileReader接口包含了一套完整的事件模型,用于捕获读取文件时的状态. 重点介绍下:readAsDataU

  • javascript HTML5文件上传FileReader API

    文件上传功能现在是越来越普遍,所有的社交网站,媒体网站,比如优酷视频,微博等,都提供了上传图片,上传视频等功能.但过去WEB程序员都很清楚,用HTML表单上传文件是很麻烦的事情,特别是你想了解一下用户上传的文件的一些属性,必须等它上传完成后才能知道. 未知的东西上传到服务器上,有可能产生安全问题,也有可能体积太大,超过允许,浪费空间.现在好了,WEB技术在进步,HTML5带来了很多好东西.这个FileReader API就能让你在用户上传之前就能获取上传文件的一些基本属性. HTML代码 这个F

  • JavaScript中的FileReader图片预览上传功能实现代码

    关于filereader图片预览上传功能的实现代码如下所示: html: <div style="width:200px;height:200px;"> <label for="ceshi" style="display:block;"> <img style="width:200px;height:200px;" id="image" src=""/>

  • 基于JavaScript FileReader上传图片显示本地链接

    简介 使用FileReader对象,web应用程序可以异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容,可以使用File对象或者Blob对象来指定所要处理的文件或数据.其中File对象可以是来自用户在一个<input type="text" />元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的 DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后的返回结果. 页面中多个,上传

  • File, FileReader 和 Ajax 文件上传实例分析(php)

    File FileReader 可以干什么? Ajax文件上传例子 FileReader 对象可以读取文件的 Base64编码数据(readAsDataURL),2进制字符串(readAsBinaryString),文本(readAsText)并且都是异步的. 对了,Email拖拽附件上传就可以利用 FileReader 配合 Ajax 完成. File 对象 File对象可以从 input[type=file].files 数组,和拖拽事件 event.dataTransfer.files 中

  • javascript结合fileReader 实现上传图片

    关于File API这里就不详细解释了,小伙伴们自行度娘吧,来我们就要利用文件句柄来读取文件内容,这是通过FileReader来实现的,通过FileReader接口,我们可以异步地将文件内容加载到内存中,赋予某个js变量. 复制代码 代码如下: function getImgSrc(target, callback) {     if (window.FileReader) {         var oPreviewImg = null, oFReader = new window.FileR

  • JavaScript使用FileReader实现图片上传预览效果

    FileReader是HTML5 File API的一部分.它实现了一种异步文件读取机制.可以把FileReader想象为XMLHttpRequest,区别只是它读取的是文件系统,而不是远程服务器.为了读取文件中的数据,FileReader提供了如下几个方法. readAsText(file,encoding):以纯文本的方式读取文件,将读取到的文件保存到result属性中. readAsDataURL(file):读取文件并将文件以数据URI的形式保存在result属性中. readAsBin

  • JavaScript html5利用FileReader实现上传功能

    本文实例为大家分享了H5利用FileReader上传文件的具体代码,供大家参考,具体内容如下 1. Html部分 <h2>文件上传演练</h2> <div id="result"> <!-- 这里用来显示读取结果 --> <div id="inResult"> <div id="inImgs"></div> <div id="imgInfo&quo

  • 借助FileReader实现将文件编码为Base64后通过AJAX上传

    使用AJAX是无法直接上传文件的,一般都是新建个iframe在它里面完成表单提交的过程以达到异步上传文件的效果. 如此做可以达到比较好的浏览器兼容性,不过代码量会比较大,即使是使用了文件上传插件,例如plupload. 如何能达到灵活的程度呢,能像普通的AJAX提交表单数据那样将文件看成是普通表单参数来对待就好了. 灵光一闪,利用javascript的FileReader对象将文件编码成base64再传服务器不就行了么~ 开始动手,丰衣足食. 前端对文件进行base64编码并通过ajax向服务器

  • JS+HTML5 FileReader实现文件上传前本地预览功能

    HTML5之FileReader的使用 HTML5定义了FileReader作为文件API的重要成员用于读取文件,根据W3C的定义,FileReader接口提供了读取文件的方法和包含读取结果的事件模型. FileReader的使用方式非常简单,可以按照如下步骤创建FileReader对象并调用其方法: 1.检测浏览器对FileReader的支持 if(window.FileReader) { var fr = new FileReader(); // add your code here } e

随机推荐