文中一开始就说,默认的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" /> <input type="submit" /> </form> <iframe></iframe>
<!--和一般的<form>标签相比多了一个target属性罢了,用于指定标签页在哪里打开以及提交数据。
如果没有设置该属性,就会像平常一样在本页重定向打开action中的url。