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

背景:

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

项目场景图:

框架组件实现源码:

ini 复制代码
//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

非框架组件方式实现

xml 复制代码
<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方法获取

相关推荐
it_remember1 小时前
新建一个reactnative 0.72.0的项目
javascript·react native·react.js
敲代码的小吉米2 小时前
前端上传el-upload、原生input本地文件pdf格式(纯前端预览本地文件不走后端接口)
前端·javascript·pdf·状态模式
da-peng-song2 小时前
ArcGIS Desktop使用入门(二)常用工具条——数据框工具(旋转视图)
开发语言·javascript·arcgis
低代码布道师3 小时前
第五部分:第一节 - Node.js 简介与环境:让 JavaScript 走进厨房
开发语言·javascript·node.js
满怀10154 小时前
【Vue 3全栈实战】从响应式原理到企业级架构设计
前端·javascript·vue.js·vue
伟笑4 小时前
elementUI 循环出来的表单,怎么做表单校验?
前端·javascript·elementui
确实菜,真的爱5 小时前
electron进程通信
前端·javascript·electron
魔术师ID6 小时前
vue 指令
前端·javascript·vue.js
Clown957 小时前
Go语言爬虫系列教程 实战项目JS逆向实现CSDN文章导出教程
javascript·爬虫·golang
星空寻流年7 小时前
css3基于伸缩盒模型生成一个小案例
javascript·css·css3