很多時候我們都會有圖片上傳的功能需求,如果我們先將圖片上傳到伺服器,然後在將返回結果顯示在前端,這樣的操作效能開銷太大,如果圖片一多,簡直要哭,而且萬一還碰到了上傳錯誤要刪除的,那簡直無法想象了。所以我們需要先將圖片在前端展示後,然後由使用者確認沒有問題了,再統一上傳,這樣才是比較理想的。
input
type=file
相信大家都知道,要在前端實現圖片的上傳,我們離不開的是一個 <input>
type=file 的 input 元素,該元素可以允許使用者選擇一個或者多個檔案。
<input type="file">複製程式碼
此時,我們點選 input 元素,就可以瀏覽本地檔案並選擇上傳。但是,此時我們只能選擇一個檔案,而不能多個。這時就需要 <input>
標籤的另一個屬性 multiple
multiple
multipla
屬性允許使用者選擇多個檔案,他是一個不需要值的屬性,也就是說,只要你的 input
標籤上出現了這個屬性,那麼不論其值是什麼,他都會支援多檔案選擇。通常來說我們使用 multiple 只會使用其屬性名,而不會給他加值
<input type='file' multiple>複製程式碼
accept
如果你嘗試了以上標籤及屬性,你會發現你的不但能選擇 image 檔案,還能選擇其他各種各樣的檔案。但是一般來說對我們有需要的只是 image 檔案,至於其他什麼的,愛咋咋地吧,只要不出來妨礙我就可以了。所以這時候我們需要 accept 的屬性來進行限制。accept 屬性接受逗號分隔的 MIME 型別字串:
1. accept='image/png' 或者 accept='.png' --只接受 .png 格式的圖片
2. accept='iamge/png,image/jpeg' 或者 accept='.png, .jpg .jpeg' 接受 .png .jpeg .jpg 格式的圖片
3. accept='image/*' 接受所有型別的 image
<input type='file' multiple accetp='image/*'>複製程式碼
注: 'image/*' 在部分瀏覽器中(Chrome和Safari等Webkit瀏覽器)響應比較緩慢,可以用以下方法代替
<<input type="file" multiple accept='image/png, image/jpeg, image/jpg, image/svg, image/gif'> 複製程式碼
樣式
一般來說我們都會將 input 設定為 display:none
, 然後通過 label 來設定其顯示樣式
// css
input{
display:none;
}
label{
// 關於label樣式
}
// html
<input type='file' multiple accept='image/png, image/jpeg, image/jpg, image/svg, image/gif' id='inputFile'>
<label for="inputFile">上傳圖片</label>複製程式碼
FileList 物件
選中檔案通過 HTMLInputElement.files 屬性返回了一個 FileList 物件,這個物件是一個包含了許多 file 檔案的列表。每個 file 物件包含了一下資訊:
1. name:檔名
2. lastModified:檔案最後一次修改時間(時間戳形式)
3. lastModifiedDate:檔案最後一次修改時間(UNIX timestamp形式)
4. size: 檔案大小(byte 為單位)
5. type:檔案 MIME 型別複製程式碼
我們可以通過對 input 標籤監聽 change 事件:
// js
document.getElementById('inputFile').addEventListener('change', changeHandler, false);
function changeHandler(e) {
var files = e.target.files;
console.log(files) // 這裡我們能獲取到所選擇的檔案資訊,需要注意的一點是 files 是個類陣列物件。
} 複製程式碼
FileReader or 物件 URL
當我們獲取到檔案物件資訊 files 了以後,我們要如何將他在頁面上預覽出來,這裡提供了兩種方法:FileReader 或者 物件 URL。
這兩種方法該如何使用,又有何區別呢?
1. FileReader
FileReader 實現了一種非同步的讀取機制。他必須先通過 FileReader()
建構函式建立出一個 fileReader 例項。該例項實現了一下幾個方法和事件(部分):
readerAsDataURL(file): 讀取檔案並以資料 URI 形式儲存在 result 屬性中
load 事件:在檔案載入成功後觸發 load 事件
error 事件:在檔案載入失敗後觸發 error 事件
progress 事件:在讀取檔案的過程中觸發 progress 事件,該事件可以近似(間隔性觸發,不是實時響應)監聽檔案上傳進度。該方法有三個屬性:lengthComputable(進度資訊是否可用), loaded(已經載入了多少), total總共有多少。
usage:
files.forEach(function(item) {
var reader = new FileReader();
reader.readAsDataURL(item);
reader.onprogress = function(e) {
if (e.lengthComputable) {
// 簡單把進度資訊列印到控制檯吧
console.log(e.loaded / e.total + '%')
}
}
reader.onload = function(e) {
var image = new Image()
image.src = e.target.result
body.appendChild(image)
}
reader.onerror = function(e) {
console.log('there is an error!')
}
})複製程式碼
2. 物件 URL
物件 URL 指的是引用儲存在 File 或 Blob 中的資料 URL。使用物件 URL 的時候不用像 FIleReader 一樣要先把資料讀取到 JavaScript 中,他可以引用 記憶體中 URL 地址而使用。
建立物件 URL 方法: window.URL.createObjectURL()。相容寫法:
function creatObjectURL(file) {
if (window.URL) {
return window.URL.createObjectURL(file);
} else if (window.webkitURL) {
return window.webkitURL.createObjectURL(file);
} else {
return null
}
}複製程式碼
usage:
files.forEach(function(item) {
var url = createObjectURL(item)
var image = new Image()
image.src = url
body.appendChild(image)
})複製程式碼
區別:
FileReader 是非同步操作,而物件 URL 是同步操作
FileReader.readAsDataURL 返回的是一個包含更多位元組的
base64
格式,createObejctURL 返回的是一個帶 hash 的 URL。由於兩者返回形式不同,FileReader.readerAsDataURL 會佔用更多記憶體,但是當你不再使用他的時候,他會自動釋放記憶體,而 createObjectURL 則只有當你的頁面關閉或者手動呼叫 revokeObejctURL 的時候才能釋放記憶體。
從相容性來說: createObjectURL 和 FileReader.readerAsDataURL 都相容 IE10+ 和現代所有主流瀏覽器
createObjectURL 相對 FileReader.readerAsDataURL,效率較高。但是如果圖片較多,則最好手動清除記憶體,可以把 URL 當做引數直接傳給 window.URL.revokeObjectURL()。相容寫法:
function revokeObjectURL(url) { if (widnow.URL) { return window.URL.revokeObjectURL(url) } else { return window.webkitURL.revokeObjectURL(url) } }複製程式碼
簡單實現:
// css
input{
display:none;
}
label{
// 關於label樣式
}
// html
<input type='file' multiple accept='image/png, image/jpeg, image/jpg, image/svg, image/gif' id='inputFile'>
<label for="inputFile">上傳圖片</label>
// js
var inputFile = document.getElementById('inputFile')
var body = document.body || document.getElementsByTagName('body')[0]
inputFile.addEventListener('change', changeHandler, false)
function changeHandler(e) {
var files = Array.from(e.target.files)
files.forEach(function(item) {
var image = new Image()
image.src = createObjectURL(item)
body.appendChild(image)
image.onload = function() {
revokeObjectURL(this.src)
}
})
}
function createObjectURL(file) {
if (window.URL) {
return window.URL.createObjectURL(file)
} else {
return window.webkitURL.createObjectURL(file)
}
}
function revokeObjectURL(file) {
if (window.URL) {
return window.URL.revokeObjectURL(file)
} else {
return window.webkitURL.revokeObjectURL(file)
}
}複製程式碼