Heim > Artikel > Web-Frontend > Sprechen Sie über drei Methoden zum Hochladen von Dateien ohne Aktualisierung, basierend auf Iframe-, FormData- und FileReader_javascript-Kenntnissen
Es gibt zwei Möglichkeiten, eine Anfrage zu senden: Eine besteht darin, Ajax zu verwenden, die andere darin, die Formularübermittlung zu verwenden. Wenn die Standardformularübermittlung nicht verarbeitet wird, wird die Seite umgeleitet. Lassen Sie uns eine einfache Demo zur Veranschaulichung verwenden:
HTML ist wie unten gezeigt, die angeforderte Pfadaktion ist „Hochladen“ und es wird keine weitere Verarbeitung durchgeführt:
<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>
Die Antwort des Servers (Knotens) gibt direkt zurück: „Formulardaten empfangen“, die Demonstration ist wie folgt:
Sie können sehen, dass das Formular standardmäßig gleichzeitig zum Hochladen auffordert und zum Hochladen weiterleitet. In vielen Fällen hoffen wir jedoch, dass die Formularanforderung wie Ajax erfolgt und die Seite nicht umgeleitet oder aktualisiert wird. Wie im obigen Szenario wird nach Abschluss des Uploads der vom Benutzer ausgewählte Avatar auf der aktuellen Seite angezeigt.
Die erste Lösung besteht darin, FormData von HTML5 zu verwenden, die Daten im Formular in das FormData-Objekt zu kapseln und sie dann per POST zu versenden. Wie im folgenden Code gezeigt, wird auf das Click-Ereignis der Schaltfläche „Senden“ reagiert. Zeile 6 des Codes ruft das DOM-Objekt des Formulars ab, erstellt dann eine Instanz von FormData in Zeile 8 und sendet die Formulardaten in Zeile 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; }
Nach erfolgreichem Hochladen gibt der Dienst die Zugriffsadresse des Bildes zurück und fügt 14 Zeilen hinzu, um die erfolgreiche Anfrage zu verarbeiten: Zeigen Sie das hochgeladene Bild über der Schaltfläche „Senden“ an:
var img = document.createElement("img"); img.src = JSON.parse(this.responseText).path; formDOM.insertBefore(img, document.getElementById("_submit"));
Beispiel:
Wenn Sie jQuery verwenden, können Sie formData als Datenparameter von Ajax verwenden und gleichzeitig contentType: false und processData: false festlegen, um jQuery anzuweisen, den Anforderungsheader und die gesendeten Daten nicht zu verarbeiten.
Es scheint, dass diese Übermittlungsmethode mit Ajax identisch ist, aber sie ist nicht genau dasselbe. Es gibt drei Datenformate für die Formularübermittlung. Wenn Sie eine Datei hochladen möchten, muss es sich also um eine mehrteilige Datei handeln http in der Formularübermittlungsanforderung oben Der Inhaltstyp in den Header-Informationen ist multipart/form-data, während die normale Ajax-Übermittlung application/json ist. Der vollständige vom Formular übermittelte Inhaltstyp lautet wie folgt:
„content-type“: „multipart/form-data; border=------WebKitFormBoundaryYOE7pWLqdFYSeBFj“
Zusätzlich zu Multipart-/Formulardaten wird auch eine Grenze angegeben. Der Zweck dieser Grenze besteht darin, verschiedene Felder zu unterscheiden. Da das FormData-Objekt undurchsichtig ist, gibt der Aufruf von JSON.stringify ein leeres Objekt {} zurück. Gleichzeitig stellt FormData nur die Append-Methode bereit, sodass der tatsächlich hochgeladene Inhalt von FormData nicht abgerufen, aber über die Daten angezeigt werden kann von Analysetools oder -diensten empfangen. Wenn Sie oben eine Textdatei hochladen, lautet das Originalformat der vom Dienst empfangenen POST-Daten wie folgt:
------WebKitFormBoundaryYOE7pWLqdFYSeBFj
Inhaltsdisposition: form-data; name="user"
abc
------WebKitFormBoundaryYOE7pWLqdFYSeBFj
Inhaltsdisposition: form-data; name="file"; filename="test.txt"
Inhaltstyp: Text/einfach
Dies ist der Inhalt einer Textdatei.
------WebKitFormBoundaryYOE7pWLqdFYSeBFj--
Aus den vom oben genannten Dienst empfangenen Daten können wir das von FormData übermittelte Format sehen. Jedes Feld ist durch eine Grenze getrennt und endet mit --. Bei Ajax-Anfragen wird das gesendete Datenformat angepasst, normalerweise mit Schlüssel=Wert und einem & in der Mitte:
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>
c17817436cd7fa6d09f2279f5c2314c2标签相比多了一个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);