前言
最近太忙一直沒時間認真的寫博客(哈哈哈),最近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的方法,但是現在瀏覽器的支持率堪憂啊,所以只有曲線救國了。
個人知識的寬度和廣度畢竟有限,如文章有什么疏漏和錯誤的地方,歡迎大家留言指出。