前言
最近太忙一直没时间认真的写博客(哈哈哈),最近pm提一个需求,移动端需要一个上传图片的功能,允许多选、删除、预览、点击查看大图并可以滑动。虽然听起来很多,但是这个功能在web上实现过啊,使用webuploader妥妥的,然后就拍着胸口答应了下来,并让B同事做。
开发完成后,后端说同一次上传多张图只能发一次请求,纳尼。。我没仔细看过webuploader的API,不知道为什么需要单独进行上传,看了一下请求信息,在支持FormData的浏览器中使用的FormData来模拟form。那既然这样也可以支持多图一起请求啊(如果webuploader支持或者有其他考虑,欢迎大家指出),算了,自己造吧,也就有了这篇随笔。
1.form表单提交
像我这样的小白都知道form表单提交之后要刷新页面,更别说要实现pm提的那些需求了,但是代码还是要上一下的。
<form action="http://baidu.com" target="" id="uploadForm" enctype="multipart/form-data"> <input id="file" type="file" name="file"/> <input type="submit" name="submit" id="submit" value="upload" /> </form>
因为我们这里要上传文件,所以enctype的值为multipart/form-data。
2.form+iframe
看到上面的代码,form有一个target的属性,规定在何处打开action,可能的值有
- _blank
- _self
- _parent
- _top
- framename
就不一一介绍了,我们最关心的是最后一个值,framename,我们将页面放在iframe里处理就不担心刷新的问题了,然后再设置一个回调就可以处理服务端返回的参数
html:
<form action="http://baidu.com" target="" id="uploadForm" enctype="multipart/form-data"> <input id="file" type="file" name="file"/> <input type="submit" name="submit" id="submit" value="upload" /> </form>
jq:
<script type="text/javascript"> var form = $("#uploadForm"); form.on("submit",function(){ var seed = Math.floor(Math.random()*1000), id = "uploader-iframe" + seed, callback = "uploader-cb" + seed, iframe = $("<iframe id='"+id+"' name='"+id+"' style='display:none'></iframe>"), url = form.attr("action"); form.attr("target",id).append(iframe).attr("action",url+"?iframe="+callback); window[callback]=function(data){ iframe.remove(); form.removeAttr("target"); form.attr("action",url); window[callback] = undefined; } }) </script>
有没有觉得和jsonp的方式有点像,但是这里不需要动态创建script标签来调用,因为iframe本来就是一个页面,只需要服务端返回调用方法和数据在iframe页面就ok了
服务端返回:
<script type="text/javascript"> window.top.window[callback](data) </script>
callback是我们事前约定好并传给服务器的参数,data为服务器返回的数据。
还有一种拿数据的方法,不通过后端回调
iframe.on("load",function(){ var ifr =$(this).contents() //jq对象document //ifr = this.contentDocument || this.document//兼容ie })
跟后端约定返回数据格式,然后进行操作
form+iframe这种伪异步的提交方式对文件的处理还是无力,不能想删就删,预览图片只有先传给后台,后台再返回一个线上的地址
3.HTML5之FormData、FileReader
当当当。。文章的主角出现
利用FormData模拟表单数据,通过ajax进行提交,FileReader的readAsDataURL方法拿到base64地址来预览(完美,注意兼容性)
form表单初始化FormData提交
<form action="http://baidu.com" target="" id="uploadForm" enctype="multipart/form-data"> <input id="file" type="file" name="file"/> <input type="submit" name="submit" id="submit" value="upload" /> </form>
$.ajax({
url: '/upload',
type: 'POST',
cache: false,
data: new FormData($('#uploadForm')[0]),
processData: false,
contentType: false
}).done(function(res) {
}).fail(function(res) {});
processData
设置为false
。因为data
值是FormData
对象,不需要对数据做处理。<form>
标签添加enctype="multipart/form-data"
属性。cache
设置为false
,上传文件不需要缓存。contentType
设置为false
。因为是由<form>
表单构造的FormData
对象,且已经声明了属性enctype="multipart/form-data"
,所以这里设置为false。
上传后服务端通过file来接收文件流。
通过FormData对象append方法来添加
<div id="uploadForm"> <input id="file" type="file"/> <button id="upload" type="button">upload</button> </div>
var formData = new FormData();
formData.append('file', $('#file')[0].files[0]);
$.ajax({
url: '/upload',
type: 'POST',
cache: false,
data: formData,
processData: false,
contentType: false
}).done(function(res) {
}).fail(function(res) {});
FileReader获取DataUrl
<input multiple="multiple" id="file" type="file" name="file"/>
var reader = new FileReader();
reader.onload=function(e){
//e.target.result为$("#file")[0].files[0]的base64地址
}
reader.readAsDataURL($("#file")[0].files[0])
更多FormData和FileReader方法可以去查看一下API
4.flash实现
flash的实现不在我们讨论的范围,而且浏览器的支持对flash有很大的影响,现在有很多上传组件做了低版本flash的兼容。比如webuploader、uploadify等
需求实现
说了这么多,最后还是要回归到需求上来,由于这个需求是在移动端上,我们自然而然就选了第三种实现方式,这里讲一下实现的思路(伪代码)。
var formdata = new FormData(), count = 0, mId = '${model.id}', a = []; formdata.append("id", mId) $("#file").on("change",function() {//file触发change时循环files做相应的处理 if ($("#file")[0].files.length > 0) { for (var i = 0, j = $("#file")[0].files.length; i < j; i += 1) { (function(k) {//按顺序插入预览图片,并在数组中保存对应的files var reader = new FileReader() reader.onload = function(e) {//保证预览顺序和files数组顺序一致,方便后面删除file count += 1; a.push($("#file")[0].files[k]); $(".filelist").append("<li><p class='imgWrap'><img class='close' src='img/close.png'/><img id='" + $("#file")[0].files[k].name + "' class='choose-img' src='" + e.target.result + "'' /></p></li>");//预览相关处理 $(".swiper-wrapper").append("<div class='swiper-slide'><img id='" + $("#file")[0].files[k].name + "' class='choose-img' src='" + e.target.result + "'' /></div>");//点击查看大图相关处理 } reader.readAsDataURL($("#file")[0].files[i]); })(i); } } }); $(document).on("click", ".close", function() {//图片删除处理,处理files数组,更新count var $this = $(this); a.splice($this.parents("li").index(), 1); $(".swiper-slide").eq($this.parents("li").index()).remove();//如果考虑复用,这个index可以优化下 count -= 1; $this.parents("li").remove(); $("#jsUpload").show(); }) $("#jsUploadBtn").on("click", function() {//上传时,将files数组循环append进FromData进行ajax提交 for (var i = 0, j = a.length; i < j; i += 1) { formdata.append("file", a[i]) } $.ajax({ url : '', type : 'POST', cache : false, data : formdata, processData : false, contentType : false }).done(function(data) { if (data == 'error') { $(".flie-toast").addClass("hide"); toast("上传失败,请联系管理员!"); } else if (data == 'no') { $(".flie-toast").addClass("hide"); toast("您没有参与比赛,不允许上传截图"); } else if (data == 'yes') { $(".flie-toast").addClass("hide"); toast("上传成功"); setTimeout(function() { window.location.href = ""; }, 2000); } else { $(".flie-toast").addClass("hide"); toast("上传失败,请联系管理员!"); } }).fail(function(res) { }); })
虽然FormData对象有一个delete的方法,但是现在浏览器的支持率堪忧啊,所以只有曲线救国了。
个人知识的宽度和广度毕竟有限,如文章有什么疏漏和错误的地方,欢迎大家留言指出。