前端文件上传功能实现原理

2024-05-18 09:29:45 浏览数 (2)

背景:

最近在做一个上传文件的功能,用的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 事件。

项目场景图:

前端文件上传功能实现原理前端文件上传功能实现原理

框架组件实现源码:

代码语言:javascript复制
//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

非框架组件方式实现

代码语言:javascript复制
<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方法获取

0 人点赞