更多文章請點選Jade
接了一個「常規」需求:開發一個本地上傳圖片控制元件,需要支援三種上傳方式:
- 支援開啟本地目錄,選擇本地圖片上傳
- 支援拖曳圖片上傳
- 支援微信截圖上傳
我們先從工程角度來看一下使用者上傳圖片的流程是怎樣子的:
- 使用者選擇了一張本地圖片,或者拖曳了一張圖片,或者通過微信截圖了一張圖片。這時,我們需要知道使用者所選擇圖片的資訊,比如圖片的內容、圖片的大小、圖片的型別等。
- 使用者上傳圖片。這時,我們需要儲存圖片。
- 使用者檢視圖片。這時,我們需要把第2步中儲存的圖片展示給使用者。
如何獲取到圖片資訊
通過input 開啟本地目錄,選擇本地圖片上傳
通過type為file的input標籤選擇本地圖片上傳,每次選擇不同圖片的時候會觸發onChange事件。為什麼我要強調不同圖片,因為當兩次選擇的圖片是一樣的情況下,onChange事件無法觸發。解決方法:每次處理完後手動置空input的value。
<input id='img-input' type='file' accept='image/*' onChange={this.bindChooseEvents} />
複製程式碼
// 置空input value
clearImgInputValue () {
document.getElementById('img-input').value = ''
}
bindChooseEvents = (e) => {
console.log('choose a image')
// 獲取File物件
const file = e.target.files[0]
let size = file.size
if (!file.type.match('image.*')) {
AntMessage.warning('File\'s type is not supported. Images only.')
this.clearImgInputValue()
return;
}
if (size > maxSize) {
AntMessage.warning('The size of the image is too large')
this.clearImgInputValue()
return;
}
/* eslint-disable */
const reader = new FileReader() // FileReader
/* eslint-disable */
// 將圖片轉換為base64
reader.readAsDataURL(file)
reader.onload = (arg) => {
// 獲取到base64圖片內容
const fileStream = arg.target.result
/**
* overwrite do something
* */
this.clearImgInputValue()
}
}
複製程式碼
拖曳圖片上傳
監聽拖曳事件,通過拖曳相關的DataTransfer物件獲取圖片資訊。
<div id='drop-zone' style={{width: '100px', height: '100px'}}>Drop Zone</div>
複製程式碼
bindDragEvents = (e) => {
const handleDragOver = (event) => {
event.stopPropagation()
event.preventDefault()
event.dataTransfer.dropEffect = 'copy'
}
// 必須阻止dragenter和dragover事件的預設行為,這樣才能觸發 drop 事件
const handleFileSelect = (event) => {
event.stopPropagation()
event.preventDefault()
const files = event.dataTransfer.files // 檔案物件
const file = files[0]
const size = file.size
const type = file.type
if (!type.match('image.*')) {
AntMessage.warning('File\'s type is not supported. Images only.')
return;
}
if (size > maxSize) {
AntMessage.warning('The size of the image is too large')
return;
}
/* eslint-disable */
const reader = new FileReader()
/* eslint-disable */
// 將圖片轉換為base64
reader.readAsDataURL(file)
reader.onload = (arg) => {
// 獲取到base64圖片內容
const fileStream = arg.target.result
/**
* overwrite do something
* */
}
}
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileSelect, false);
}
複製程式碼
微信截圖上傳
監聽paste事件,通過剪貼簿物件clipboardData獲取圖片資訊。
bindClipEvents() {
document.addEventListener('paste', (e) => {
console.log('paste a image')
const clipboard = e.clipboardData
// 有無內容
if (!clipboard.items || !clipboard.items.length) {
AntMessage.warning('No content in the clipboard')
return;
}
let item = clipboard.items[0]
if (item.kind === 'file' && item.type.match('image.*')) {
// 獲取圖片檔案
let imgFile = item.getAsFile()
if (imgFile.size > maxSize) {
AntMessage.warning('The size of the image is too large')
return;
}
const reader = new FileReader()
// 將圖片轉換為base64
reader.readAsDataURL(imgFile)
reader.onload = (arg) => {
// 獲取到base64圖片內容
const fileStream = arg.target.result
/**
* overwrite do something
* */
}
} else {
AntMessage.warning('File\'s type is not supported. Images only.')
}
}, false)
}
複製程式碼
如何儲存
圖片儲存一般都採用獨立圖片獨立域名伺服器,不會傻乎乎地把圖片儲存在web伺服器上也不會直接存在專案表的資料庫中。這樣做有什麼好處呢?
- 圖片訪問是I/O密集型操作,很消耗伺服器資源,從Web伺服器獨立出來後,能夠減少Web伺服器壓力
- 便於擴容、容災和資料遷移
- 瀏覽器有同域名下的併發策略限制
- 請求圖片一般並不需要cookie,但是瀏覽器發起的所有同域名請求時,http頭部都會自動帶上cookie資訊,導致浪費頻寬
- 方便對圖片訪問做負載均衡,可以對圖片應用各種快取策略
- 方便遷移CDN ...
總結&優化
流程圖:
不足:
專案實踐中我們雖然採用了獨立圖片伺服器,下載過程只是通過Web伺服器去資料庫拿到圖片地址,但是我們的上傳操作仍舊經過了Web伺服器,需要Web伺服器上的應用程式來處理,所以上傳過程仍舊對Web伺服器造成壓力。所幸的是,我們對圖片上傳大小進行了1M的大小限制,同時作為內部系統沒啥訪問壓力,而且圖片上傳功能也並不是很高發的行為,所以這麼做基本也不會有啥問題。但是最好的方案還是不管下載上傳都直接走獨立圖片伺服器,避免對Web伺服器造成額外的壓力。