iframe、FormData、FileReader_javascript のスキルに基づいて、更新せずにファイルをアップロードする 3 つの方法について説明します。

WBOY
リリース: 2016-05-16 15:27:58
オリジナル
1310 人が閲覧しました

リクエストを送信するには 2 つの方法があります。1 つは ajax を使用する方法、もう 1 つはフォーム送信を使用する方法です。デフォルトのフォーム送信が処理されない場合、ページはリダイレクトされます。簡単なデモを使って説明してみましょう:


html は次のとおりです。要求されたパス アクションは「アップロード」であり、他の処理は行われません。

 <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> 
ログイン後にコピー

サーバー (ノード) の応答は、「受信したフォーム データ」を直接返します。デモは次のとおりです。

デフォルトでは、フォームがアップロードを要求し、同時にアップロードにリダイレクトしていることがわかります。ただし、多くの場合、フォーム リクエストが ajax のようになり、ページのリダイレクトや更新が行われないことが望まれます。上記のシナリオと同様に、アップロードが完了すると、ユーザーが選択したアバターが現在のページに表示されます。

最初の解決策は、html5 の FormData を使用し、フォーム内のデータを FormData オブジェクトにカプセル化し、それを POST で送信することです。次のコードに示すように、コードの 6 行目でフォームの DOM オブジェクトを取得し、8 行目で FormData のインスタンスを構築し、18 行目でフォーム データを送信します。

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 行を追加します。アップロードされた画像を送信ボタンの上に表示します。

var img = document.createElement("img");
     img.src = JSON.parse(this.responseText).path;
     formDOM.insertBefore(img, document.getElementById("_submit")); 
ログイン後にコピー

例:


jQuery を使用する場合は、ajax の data パラメーターとして formData を使用し、contentType: false と processData: false を同時に設定して、jQuery にリクエスト ヘッダーと送信されたデータを処理しないように指示できます。

この送信方法は ajax と同じように見えますが、まったく同じではありません。フォーム送信には 3 つのデータ形式があるため、ファイルをアップロードする場合は multipart/form-data でなければなりません。上記のフォーム送信リクエストの http ヘッダー情報の Content-Type は multipart/form-data ですが、通常の ajax 送信は application/json です。フォームによって送信された完全な Content-Type は次のとおりです:

"コンテンツタイプ":"マルチパート/フォームデータ; 境界=-----WebKitFormBoundaryYOE7pWLqdFYSeBFj"

multipart/form-data に加えて、境界も指定されます。この境界の目的は、さまざまなフィールドを区別することです。 FormData オブジェクトは不透明であるため、JSON.stringify を呼び出すと空のオブジェクト {} が返されます。同時に、FormData は append メソッドのみを提供するため、実際にアップロードされた FormData のコンテンツは取得できませんが、データを通じて表示できます。分析ツールまたはサービスによって受信されます。上記のテキスト ファイルをアップロードした場合、サービスが受信する POST データの元の形式は次のとおりです:

------WebKitFormBoundaryYOE7pWLqdFYSeBFj

コンテンツの配置: フォームデータ名 = "ユーザー"

ABC

------WebKitFormBoundaryYOE7pWLqdFYSeBFj

コンテンツの配置: フォームデータ; ファイル名 = "test.txt"; コンテンツタイプ: text/plain

これはテキスト ファイルの内容です。

------WebKitFormBoundaryYOE7pWLqdFYSeBFj--

上記のサービスで受信したデータから、FormData によって送信された各フィールドが境界で区切られ、- で終わっていることがわかります。 Ajax リクエストの場合、送信されるデータ形式はカスタマイズされており、通常は 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> 
ログイン後にコピー