場景
最近,小明遇到這樣一種情況:在網頁中上傳文件時偶爾頁面會崩潰。小明仔細測試了這種情況,發現之前用的一個文件上傳組件有一點缺陷,於是,小明決定自己手寫一個,樣式如下:
圖一是沒有上傳文件時的樣式,圖二爲上傳文件後的樣式。虛線部分爲放置區域,先來看代碼:
html部分
<div id="app">
<div class="content">
<div class="drag-area" @dragover="fileDragover" @drop="fileDrop">
<div v-if="fileName" class="file-name">{{ fileName }}</div>
<div v-else class="uploader-tips">
<span>將文件拖拽至此,或</span>
<label for="fileInput" style="color: #11A8FF; cursor: pointer">點此上傳</label>
</div>
</div>
</div>
<div class="footer">
<input type="file" id="fileInput" @change="chooseUploadFile" style="display: none;">
<label for="fileInput" v-if="fileName" style="color: #11A8FF; cursor: pointer">選擇文件</label>
<button @click="uploadOk">提交</button>
</div>
</div>
css部分
* {
font-size: 14px;
}
.drag-area {
height: 200px;
width: 300px;
border: dashed 1px gray;
margin-bottom: 10px;
color: #777;
}
.uploader-tips {
text-align: center;
height: 200px;
line-height: 200px;
}
.file-name {
text-align: center;
height: 200px;
line-height: 200px;
}
js部分
new Vue({
el: '#app',
data () {
return {
fileName: '',
batchFile: '',
MAX_FILE_SIZE: 10 * 1000 * 1000
}
},
methods: {
// 點擊上傳
chooseUploadFile (e) {
const file = e.target.files.item(0)
if (!file) return
if (file.size > this.MAX_FILE_SIZE) {
return alert('文件大小不能超過10M')
}
this.batchFile = file
this.fileName = file.name
// 清空,防止上傳後再上傳沒有反應
e.target.value = ''
},
// 拖拽上傳
fileDragover (e) {
e.preventDefault()
},
fileDrop (e) {
e.preventDefault()
const file = e.dataTransfer.files[0] // 獲取到第一個上傳的文件對象
if (!file) return
if (file.size > this.MAX_FILE_SIZE) {
return alert('文件大小不能超過10M')
}
this.batchFile = file
this.fileName = file.name
},
// 提交
uploadOk () {
if (this.batchFile === '') {
return alert('請選擇要上傳的文件')
}
let data = new FormData()
data.append('upfile', this.batchFile)
// ajax
}
}
})
核心原理說明
- dragover和drop事件
第一個要說的就是拖拽中的這兩個事件,因爲這兩個事件撐起了拖拽上傳的核心功能。
對於拖拽這個動作而言,有二個核心概念,一個是拖拽元素
,還一個是放置目標
。這裏,我只講放置目標上的事件,對於拖拽元素的事件,請自行查閱。
那對於放置目標,它有什麼事件呢?如下:
當某個元素被拖動到一個有效的放置目標上(如上例中虛線區域)時,下列事件會依次發生:
(1) dragenter
(2) dragover
(3) dragleave 或 drop
只要有元素被拖動到放置目標上,就會觸發 dragenter 事件(類似於 mouseover 事件)。緊隨其後的是 dragover 事件,而且在被拖動的元素還在放置目標的範圍內移動時,就會持續觸發該事件。如果元素被拖出了放置目標,dragover 事件不再發生,但會觸發 dragleave 事件(類似於 mouseout事件)。如果元素被放到了放置目標中,則會觸發 drop 事件而不是 dragleave 事件。
對於本例來說,我們只需要關注dragover和drop事件。但是drop事件卻有點調皮,你想監聽它,還得進行一些處理,因爲默認情況下,元素是不允許放置的,在拖動元素經過某些無效放置目標時,可以看到一種特殊的光標(圓環中有一條反斜線),表示不能放置。如下:
如果拖動元素經過不允許放置的元素,那無論用戶如何操作,都不會發生 drop 事件。那怎麼辦呢?
我們可以重寫 dragover 事件的默認行爲,如上例代碼中的e.preventDefault()
。
細心的同學可能要問了,那drop事件中也有e.preventDefault()
,去掉行不行呢?大家可以自行試下。
- dataTransfer 對象
可能這個對象看着有些陌生,但是它的作用可不小。比如,你拖動一個圖片到目標區域,那目標區域怎麼獲取這個圖片的信息呢?就靠它!它是事件對象的一個屬性,用於從被拖動元素向放置目標傳遞字符串格式的數據。在本例中,我們可以通過它來獲取拖動中的文件信息。
- input的change事件
這個事件其實有坑的,它有這樣一個特性,即:上傳同一個文件,並不會觸發change事件,即使該文件內容做過修改。
細思極恐!比如,用戶要上傳一個文檔,但是拖拽到虛線區域後發現文檔內容還需要修改下,他改完後再拖拽該文檔,再提交到服務器,那麼他上傳到服務器的文檔內容卻是未修改之前的!
所以,我們需要代碼e.target.value = ''
來進行重置處理,這樣,每次上傳文件都會觸發change事件。