最近很好奇前端的文件上傳功能,因為公司要求做一個支持圖片預覽的圖片上傳插件,所以自己搜了很多相關的插件,雖然功能很多,但有些地方不能根據公司的想法去修改,而且需要依賴jQuery或Bootstrap庫,所以我就想學下圖片上傳的原理,試着做一個原生無依賴,而且足夠靈活的圖片上傳插件。話不多說,開整。
1. 大體思路
1.1 首先我們需要考慮用戶如何使用我們的插件。
用戶引入插件代碼后,只需要像下面這樣,設置一些參數,然后執行一個方法就生成一個圖片上傳組件。
<div id="upload"></div> // 這是用來生成圖片上傳組件的div
<script>
// 設置參數
var options = {
path: '/', // 上傳圖片時指定的地址路徑,類似form變淡的action屬性
onSuccess: function (res) { // 上傳成功后執行的方法,res是接收的ajax響應內容
console.log(res);
},
onFailure: function (res) { // 上傳失敗后執行的方法,res是接收的ajax響應內容
console.log(res);
}
}
// 執行生成圖片上傳插件的方法, 第一個參數是上面提到的准備生成組件的div選擇器,第二個參數是設置的組件信息,執行方法后返回一個函數指針,指向執行上傳功能的函數,通過把執行上傳功能的函數暴露出來,用戶就可以自己控制何時上傳圖片了。
var upload = tinyImgUpload('#upload', options);
</script>
1.2 代碼設計
我們需要思考用戶如何引入我們的插件代碼。
插件代碼應該分為兩個文件,一個CSS文件(tinyImgUpload.css),用於定義組件的基本樣式,此外用戶可以根據自己的想法自己DIY樣式,另一個是控制功能邏輯的js文件(tinyImgUpload.js)。用戶引入這兩個文件后,就可以實現圖片上傳組件了。
2. 具體實現
具體實現的時候,主要涉及到兩個地方,一個是讀取本地文件,實現圖片上傳前可以預覽的功能,一個是圖片上傳功能。
2.1 讀取本地文件實現預覽
這里用到了html5的File API,使用這個API可以在客戶端驗證上傳的文件類型,限制文件大小,當然,在這里我們主要用到FileReader接口來讀取文件,Filereader.readAsDataURL()返回的事件對象的result屬性就是將文件編碼為base64的數據地址,類似下面這樣的,把他賦值給src屬性,圖片就顯示出來了。

具體代碼如下,完整代碼可以從這里下載(tinyImgUpload.js )
// 預覽圖片
//處理input選擇的圖片
function handleFileSelect(evt) {
var files = evt.target.files;
for(var i=0, f; f=files[i];i++){
// 過濾掉非圖片類型文件
if(!f.type.match('image.*')){
continue;
}
// 過濾掉重復上傳的圖片
var tip = false;
for(var j=0; j<(ele.files).length; j++){
if((ele.files)[j].name == f.name){
tip = true;
break;
}
}
if(!tip){
// 圖片文件綁定到容器元素上
ele.files.push(f);
var reader = new FileReader();
reader.onload = (function (theFile) {
return function (e) {
var oDiv = document.createElement('div');
oDiv.className = 'img-thumb img-item';
// 向圖片容器里添加元素
oDiv.innerHTML = '<img class="thumb-icon" src="'+e.target.result+'" />'+
'<a href="javscript:;" class="img-remove">x</a>'
ele.insertBefore(oDiv, addBtn);
};
})(f);
reader.readAsDataURL(f);
}
}
}
// input#img-file-input是一個隱藏的上傳圖片的input控件,當選擇圖片的時候會觸發change事件
document.querySelector('#img-file-input').addEventListener('change', handleFileSelect, false);
2.2 上傳圖片
2.2.1 准備文件對象
上傳文件之前,我們需要考慮如何保存用戶已經選擇的文件對象,由於用戶可能多次選擇,也可能在上傳之前又刪除了幾個圖片,所以需要有一個地方實時保存圖片信息,並且要和預覽的圖片保持同步,預覽顯示有哪幾張圖片,這個地方就存儲幾張圖片。我采用的方式是將文件信息組裝成一個數組,然后綁定到組件元素(#img-container)的自定義屬性上,上面代碼中的“ele.files.push(f)”做的就是這件事。
2.2.2 文件對象我們准備好后,下一步就是上傳了
ajax是不能直接上傳文件對象的,我們可以通過FormData對象,FormData是XMLHttpRequest Level 2添加的一個新接口,使用一系列的鍵值對來模擬一個完整的表單,然后使用XMLHttpRequest異步發送這個"表單"。具體代碼如下。
// 上傳圖片
function uploadImg() {
var xhr = new XMLHttpRequest();
var formData = new FormData();
for(var i=0, f; f=ele.files[i]; i++){
formData.append('files', f);
}
xhr.onreadystatechange = function (e) {
if(xhr.readyState == 4){
if(xhr.status == 200){
options.onSuccess(xhr.responseText);
}else {
options.onFailure(xhr.responseText);
}
}
}
xhr.open('POST', options.path, true);
xhr.send(formData);
}
2.3 設置樣式
我們寫了一個基本的布局樣式作為默認樣式,用戶可以根據自己的需求進行DIY。這里是完整的樣式文件(tinyImgUpload.css )。
基本的效果圖如下。

如果我們想觸發上傳圖片,可以把tinyImgUpload('#upload', options)返回的upload方法綁定到一個按鈕上,監聽點擊事件。
<button class="submit">submit</button>
<script>
document.getElementsByClassName('submit')[0].onclick = function (e) {
upload();
}
</script>
這樣當我點擊圖片的時候,圖片就會上傳,交給服務器端處理了。

上傳按鈕

服務器接收的圖片
為了測試圖片上傳好不好用,我自己搭建了一個圖片接收的服務器,使用的是node.js,通過multer實現,如果大家感興趣可以點擊這里。
3 總結
圖片上傳的關鍵部分就是如何讀取本地文件實現預覽,以及通過FormData對象構造一個表單對象實現ajax異步上傳文件。目前這個插件的功能還不夠完善,我把它放到了Github上(https://github.com/gitwd/tinyImgUpload),后續會慢慢優化,歡迎大家提出寶貴意見。
4 參考目錄
https://www.html5rocks.com/en/tutorials/file/dndfiles/
http://codecloud.net/9276.html
http://www.zhangxinxu.com/wordpress/2011/09/基於html5的可預覽多圖片ajax上傳/
https://cnodejs.org/topic/50ce2bbb637ffa415589a50f
