DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> 談談基於iframe、FormData、FileReader三種無刷新上傳文件的方法
談談基於iframe、FormData、FileReader三種無刷新上傳文件的方法
編輯:關於JavaScript     

發請求有兩種方式,一種是用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);
XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved