業(yè)務(wù)需求分析
簡單來說隧魄,需在前端實現(xiàn)搜索圖片的交互藻糖。流程來看,無非先本地選中圖片叮喳,瀏覽器端用戶可以對圖片進(jìn)行相應(yīng)的裁剪被芳,將裁剪后的參數(shù)和圖片本身異步上傳到服務(wù)器,后臺分析圖片信息嘲更,裁剪圖片上傳筐钟,確認(rèn)上傳完畢后,再讓前端以GET的形式跳轉(zhuǎn)到搜圖結(jié)果頁面赋朦,完成搜索篓冲。本文只討論前端實現(xiàn)。
我的做法 (環(huán)境是jQuery)
用戶點擊搜圖按鈕的時候宠哄,加載一個全新的Form表單壹将,并且將其隱藏
<form id="my_form" style="display:none"
enctype="multipart/form-data">
<input type="file" name="inputname" class="inputname" id="inputname">
</form>
在同一個回調(diào)方法體內(nèi),觸發(fā)input file 的點擊
$('#inputname').click();
此時用戶瀏覽器將彈出一個資源窗口毛嫉,選中具體的圖片诽俯,點擊確定后將觸發(fā)綁定在$('#inputname')
的change事件,事件回調(diào)中異步提交表單
$('#my_form').ajaxSubmit();//偽代碼承粤,jQuery原生不支持
關(guān)于異步提交帶文件的二進(jìn)制表單暴区,用jQuery原生并不能很好的實現(xiàn),因此借助了FormData對象
$.ajax({
url: 'http://yourdomain/upload',
data: new FormData($('#my_form')[0]),
method: 'post',
dataType: 'json',
processData: false,
contentType: false
}).done(function(){
alert('OK');
}).fail(function () {
alert('FAILED');
});
剩下回調(diào)后的處理我就不多說辛臊,也并非本文的重點仙粱。
遇到的問題
既然是IE8填坑之旅,還是應(yīng)該著重說說坑彻舰。理論上來講伐割,支持ES5規(guī)范的瀏覽器,在上述流程中都不會出現(xiàn)問題刃唤。然而IE8依舊有龐大的市場份額...
IE8的安全機(jī)制
問題描述
由于表單是通過change事件回調(diào)函數(shù)里提交的隔心,而觸發(fā)change事件本身并非直接點擊input file按鈕并在資源窗口選擇文件后觸發(fā)的,而是間接通過點擊其它DOM結(jié)構(gòu)的回調(diào)中觸發(fā)了input file 的點擊事件尚胞。那么問題來了硬霍,IE8可能會把這個當(dāng)做是非用戶觸發(fā)的事件,不允許進(jìn)行任何表單提交操作辐真。題外話须尚,這個與window.open(url,'_blank')
的限制機(jī)制異曲同工崖堤。
解決思路
因為之前沒有任何處理IE8的經(jīng)驗,解決過程比較坎坷耐床。
最簡單的思路是密幔,既然IE8對此有安全限制,那么是否有辦法讓用戶直接點擊input file撩轰,直接觸發(fā)change事件回調(diào)胯甩,提交表單?
流程走下來固然可行堪嫂,可是也有問題偎箫,我們都知道要想把input file控件的樣式改成自己想要的樣式,而且無法借助css3的前提下皆串,是非常麻煩的淹办,那么索性,同時也是借鑒淘寶的做法恶复,把整個input file控件做成透明的怜森,把自己預(yù)先定義好的按鈕,或是字體谤牡,或是圖片副硅,統(tǒng)統(tǒng)放到透明控件下面,這樣用戶看到的是你定義的按鈕或是icon翅萤,實際上點擊的則是input file控件本身恐疲,沒有半點違和感。
當(dāng)然這么處理仍然不完美套么,我們都知道input file在IE8默認(rèn)樣式分兩個部分培己,一部分是輸入框,一部分是帶有"瀏覽"字樣的按鈕胚泌,點擊瀏覽按鈕可以彈出資源窗口漱凝,而輸入框則需要雙擊才能彈出資源窗口,那么如果你自定義的按鈕或者icon比input file中"瀏覽"按鈕大诸迟,如何能確保"瀏覽"按鈕尺寸足夠大不出錯呢?這里有一個比較巧妙的辦法愕乎,通過font-size
屬性可以把按鈕撐大阵苇,然后通過css定位一下確保完全能夠覆蓋自己定義的范圍就可以了。下面給出Demo感论。
.inputname{
width: 600px;
height: 600px;
position: absolute;
top:0;
right: 0px;
z-index: 9999;
display: block;
cursor: pointer;
font-size: 100px;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
}
FormData不支持IE10以下的瀏覽器
解決思路
這個解決方式就比較簡單了绅项,很多overflowstack網(wǎng)友都推薦了一個輪子jquery.form.js,參考了jquery.form.js的源碼以及其他聲稱自己能夠完美兼容IE8的異步提交帶文件的二進(jìn)制表單的插件比肄,我發(fā)現(xiàn)如果要在不支持FormData屬性的瀏覽器環(huán)境中做到異步提交快耿,大家的做法都很統(tǒng)一囊陡。
思路大概是表單提交的時候,預(yù)先在用戶看不到的地方生成一個空白iframe掀亥,并將form的target
屬性設(shè)置為該空白iframe的name
撞反。
這樣一來,表單提交后本頁就不會跳轉(zhuǎn)或刷新搪花,返回的XML遏片、JSON、PLAIN TEXT或是HTML的內(nèi)容都會在空白iframe里加載出來撮竿,只需要開發(fā)者預(yù)先監(jiān)聽該iframe的load
事件吮便,并把iframe中加載出來的內(nèi)容轉(zhuǎn)換成所需要的數(shù)據(jù)格式進(jìn)行判斷就可以完成異步提交了。
//不是jQuery.form.js源碼
iframeObj.bind("load", function () {
var contents = $(this).contents().get(0);
var data = $(contents).find('body').text();
if ('json' == options.dataType) {
data = window.eval('(' + data + ')');
}
options.onSuccess(data);
iframeObj.remove();
form.remove();
iframeObj = null;
});
form.submit();
IE8各種PolyFills
由于各種瀏覽器ES規(guī)范不一致幢踏,因此就決定了每個項目或多或少都要寫一些必要的PolyFills髓需,同時也是為了獲取更方便的開發(fā)體驗。
項目在用的PolyFills(Object.bind函數(shù)和Array.filter函數(shù))
/**
* 針對ES兼容性的寫法
*/
module.exports = {
makePolyFill : function(){
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {
},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
if (!Array.prototype.filter) {
Array.prototype.filter = function (fun /*, thisp */) {
"use strict";
if (this === void 0 || this === null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function")
throw new TypeError();
var res = [];
var thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i]; // in case fun mutates this
if (fun.call(thisp, val, i, t))
res.push(val);
}
}
return res;
};
}
}
}
小結(jié)
如果沒有耐心房蝉,兼容問題根本處理不來僚匆。上世紀(jì)末到現(xiàn)在,我們經(jīng)歷了瀏覽器陣營的各種混戰(zhàn)惨驶,實際上隨著ES標(biāo)準(zhǔn)不斷更迭白热,和各廠商的推進(jìn)努力,瀏覽器兼容的問題相比以前容易處理很多粗卜。加之有許多優(yōu)秀的CSS resets以及瀏覽器特性檢測框架(如Modernizr
)屋确,更撫慰了無數(shù)前端的小心臟。
參考資料
FormData API续扔;
jQuery.form.js官網(wǎng)攻臀;
Github上不錯的PolyFills;
modernizr官網(wǎng)纱昧;