背景:
最近在做一个上传文件的功能,用的elementUI框架的el-upload组件,为了探究其原理,就想到了有两种上传方式,第一种是type为file的input选择上传,另一个就是拖拽的上传方式,拖拽一直没弄清原理,借这次机会彻底搞清楚。
HTML5实现拖放功能
有两个核心元素拖拽元素和放置目标元素,通过这两个元素的触发事件来实现拖放功能。
(1)拖拽元素要赋予draggable属性,属性值为true
(2)放置目标元素要在dragover和dragenter事件值中阻止默认行为
(3)放置目标元素在drop事件里可通过DataTransfer对象获取拖拽元素信息
拖拽元素和目标元素的属性和事件:
-
DataTransfer 对象:拖拽对象用来传递的媒介,使用一般为Event.DataTransfer。
-
draggable 属性:就是标签元素要设置draggable=true
-
ondragstart 事件:当拖拽元素开始被拖拽的时候触发的事件,此事件作用在被拖曳元素上
-
ondragenter 事件:当拖曳元素进入目标元素的时候触发的事件,此事件作用在目标元素上
-
ondragover 事件:拖拽元素在目标元素上移动的时候触发的事件,此事件作用在目标元素上
-
ondrop 事件:被拖拽的元素在目标元素上同时鼠标放开触发的事件,此事件作用在目标元素上
-
ondragend 事件:当拖拽完成后触发的事件,此事件作用在被拖曳元素上
-
Event.preventDefault()方法:阻止默认的些事件方法等执行。在ondragover中一定要执行preventDefault(),否则ondrop事件不会被触发。另外,如果是从其他应用软件或是文件中拖东西进来,尤其是图片的时候,默认的动作是显示这个图片或是相关信息,并不是真的执行drop。此时需要用用document的ondragover事件把它直接干掉。
Event.effectAllowed 属性:就是拖拽的效果。
放置目标事件顺序:
(1) dragenter
(2) dragover
(3) dragleave 或 drop
只要有元素被拖动到放置目标上,就会触发 dragenter 事件(类似于 mouseover 事件)。紧随其后的是 dragover 事件,而且在被拖动的元素还在放置目标的范围内移动时,就会持续触发该事件。如果元素被拖出了放置目标,dragover 事件不再发生,但会触发 dragleave 事件(类似于 mouseout事件)。如果元素被放到了放置目标中,则会触发 drop 事件而不是 dragleave 事件。
项目场景图:
框架组件实现源码:
//html
<el-upload
multiple
ref="upload"
:limit="7"
accept=".json"
:action="uploadMapData.importUrl"
:disabled="uploadMapData.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将json数据拖拽至此处或<em>点击上传</em></em></div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm">确定</el-button></el-button>
<el-button @click="uploadMapData.open = false">取消</el-button>
</div>
//js
非框架组件方式实现
<template>
<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="submit">提交</button>
</div>
</div>
</template>
<script>
export default {
name: 'app',
data(){
return{
fileName:'',
batchFile:'',
maxFileSize:10*1000*1000
}
},
methods:{
// 上传方式获取文件
chooseUploadFile(e){
console.log(e.target.value)
const file = e.target.files.item(0)
if(!file)return
if(file.size > this.maxFileSize){
return alert('文件超过大小')
}
this.batchFile = file
this.fileName = file.name
//上传后记得要清空,防止修改文件后再次上传没有反应,这是input的file类型bug
e.target.value = ''
},
// 阻止放置目标元素的dragover的默认行文
fileDragover(e){
e.preventDefault()
},
// 拖拽方式获取文件
fileDrop(e){
e.preventDefault()
const file = e.DataTransfer.files[0] //获取第一个上传的文件对象
if(!file)return
if(file.size > this.maxFileSize){
return alert('文件超过大小')
}
this.batchFile = file
this.fileName = file.name
},
async submit(){
if(!this.batchFile){
return alert('请选择要上传的文件')
}
let formData = new FormData()
formData.append('file',this.batchFile) //FormData 接口的 append() 方法 会添加一个新值到 FormData 对象内的一个已存在的键中,如果键不存在则会添加该键
//ajax...
let res = await this.$app.uploadToQiniu(formData)
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
* {
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;
}
</style>
.item(index)介绍
在 HTML 文件上传中,
<input type="file">
元素允许用户选择一个或多个文件进行上传。当用户选择文件后,浏览器会将文件信息存储在FileList
对象中,该对象是一个类似数组的对象,表示用户选择的文件列表。
FileList
对象有一个item(index)
方法,该方法用于获取指定索引位置的文件。文件列表是从 0 开始的,所以.item(0)
表示获取文件列表中的第一个文件。因此,当你使用
e.target.files.item(0)
时,你实际上是在获取用户选择的文件列表中的第一个文件。如果用户选择了多个文件,你可以使用.item(1)
获取第二个文件,以此类推。
小思考:为啥不能在input标签上用vue的v-model方式获取文件?
input type="file"的input标签不能通过v-model获取文件,只能通过change方法获取