国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

談談基于iframe、FormData、FileReader三種無刷新上傳文件的方法

2019-11-20 11:05:40
字體:
來源:轉載
供稿:網(wǎng)友

發(fā)請求有兩種方式,一種是用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里面的數(shù)據(jù)封裝到FormData對象里,然后再以POST的方式send出去。如下面代碼所示,對提交按鈕的單擊事件做一個響應,代碼第6行獲取到form的DOM對象,然后第8行構造一個FormData的實例,第18行,將form數(shù)據(jù)發(fā)送出去。

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的構造函數(shù)   var formData = new FormData(formDOM);   var req = new XMLHttpRequest();   req.open("POST", "upload");   //請求完成   req.onload = function(){    if(this.status === ){      //對請求成功的處理    }   }   //將form數(shù)據(jù)發(fā)送出去   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參數(shù),同時設置contentType: false和processData: false,告訴jQuery不要去處理請求頭和發(fā)送的數(shù)據(jù)。

      看起來這種提交方式跟ajax一樣,但是其實并不是完全一樣,form提交的數(shù)據(jù)格式有三種,如果要上傳文件則必須為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的作用是用來區(qū)分不同的字段。由于FormData對象是不透明的,調用JSON.stringify將會返回一個空的對象{},同時FormData只提供append方法,所以無法得到FormData實際上傳的內容,但是可以通過分析工具或者服務收到的數(shù)據(jù)進行查看。在上面如果上傳一個文本文件,那么服務收到的POST數(shù)據(jù)的原始格式是這樣的:

------WebKitFormBoundaryYOE7pWLqdFYSeBFj

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

abc

------WebKitFormBoundaryYOE7pWLqdFYSeBFj

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

這是一個文本文件的內容。

------WebKitFormBoundaryYOE7pWLqdFYSeBFj--

     從上面服務收到的數(shù)據(jù)看出FormData提交的格式,每個字段以boundary隔開,最后以--結束。而ajax請求,send出去的數(shù)據(jù)格式是自定義的,一般都是以key=value中間用&連接:

 var req = new XMLHttpRequest();  var sendData = "user=abc&file=這是一個文本文件的內內容";  req.open("POST", "upload");  //發(fā)送的數(shù)據(jù)需要轉義,見上面提到的三種格式  req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");  req.send(sendData); 

      服務就會收到和send發(fā)出去的字符串一模一樣的內容,然后再作參數(shù)解析,所以就得統(tǒng)一參數(shù)的格式:

user=abc&file=這是一個文本文件的內容

      從這里可以看出POST本質上并不比GET安全,POST只是沒有將數(shù)據(jù)放在網(wǎng)址傳送而已。

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

      文中一開始就說,默認的form提交會使頁面重定向,而重定向的規(guī)則在target中指定,可以和a標簽一樣指定為"_blank",在新窗口中打開;還可以指定為一個iframe,在該iframe中打開。所以可以弄一個隱藏的iframe,將form的target指向這個iframe,當form請求完成時,返回的數(shù)據(jù)就會由這個iframe顯示,正如上面在新頁面顯示的:"Recieved form data"。請求完成后,iframe加載完成,觸發(fā)load事件,在load事件的處理函數(shù)里,獲取該iframe的內容,從而拿到服務返回的數(shù)據(jù)了!拿到后再把iframe刪掉。

      在提交按鈕的響應函數(shù)里,首先創(chuàng)建一個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的內容,即服務返回的數(shù)據(jù)   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函數(shù)不存在,請注意form里面是否有id/value為submit的控件   this.form.submit();  } 

      第二種辦法到這里就基本可以了,但是如果看163郵箱或者QQ郵箱上傳文件的方式,會發(fā)現(xiàn)和上面的兩種方法都不太一樣。用httpfox抓取請求的數(shù)據(jù),會發(fā)現(xiàn)上傳的內容的格式并不是上面說的用boundary隔開,而是直接把文件的內容POST出去了,而文件名、文件大小等相關信息放在了文件的頭部。如163郵箱:

POST Data:

    this is a text

Headers:

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

      可以推測它們應該是直接讀取了input文件的內容,然后直接POST出去了。要實現(xiàn)這樣的功能,可以借助FileReader,讀取input文件的內容,再保留二進制的格式發(fā)送出去: 

 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行執(zhí)行讀文件,讀取完畢后觸發(fā)第6行的load響應函數(shù),第7行以二進制文本形式發(fā)送出去。由于sendAsBinary的支持性不是很好,可以自行實現(xiàn)一個:

 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位無符號整型,還原二進制文件的內容。在執(zhí)行了fr.readAsBinaryString之后,二進制文件的內容將會以utf-8的編碼以字符串形式存放到result,上面的第6行代碼將每個unicode編碼轉成整型(&0xff或者parseInt),存放到一個8位無符號整型數(shù)組里面,第8行把這個數(shù)組發(fā)送出去。如果直接send,而不是sendAsBinary,服務收到的數(shù)據(jù)將無法正常還原成原本的文件。

     上面的實現(xiàn)需要考慮文件太大,需分段上傳的問題。

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

     文章討論了3種辦法實現(xiàn)無刷新上傳文件,分別是使用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屬性罷了,用于指定標簽頁在哪里打開以及提交數(shù)據(jù)。

如果沒有設置該屬性,就會像平常一樣在本頁重定向打開action中的url。

而如果設置為iframe的name值,即"upload"的話,就會在該iframe內打開,因為CSS設置為隱藏,因而不會有任何動靜。若將display:none去掉,還會看到服務器的返回信息。 

--> upload.php<?phpheader("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一維數(shù)組  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("http://","/",$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);
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 澄城县| 防城港市| 武汉市| 静安区| 区。| 合肥市| 石城县| 卓尼县| 新干县| 彝良县| 马公市| 布尔津县| 土默特左旗| 桓仁| 常宁市| 庄浪县| 叙永县| 华蓥市| 科技| 佛冈县| 如皋市| 普格县| 密山市| 尼木县| 星座| 琼中| 和静县| 佛冈县| 内黄县| 建昌县| 诸暨市| 湘西| 九龙坡区| 罗平县| 洪江市| 五常市| 苍梧县| 海丰县| 偏关县| 辽宁省| 莱阳市|