使用 type='file' 的 <input> 元素可以选择文件,基于此我们可以封装自定义的上传组件,<input type="file"> 可以接收四个附加属性:
accept: 字符串,允许选择的文件类型,如: ".jpg,.jpeg,.png,.gif"
multiple: 布尔值,是否允许多选
capture: 字符串,可以调用设备默认的相机或录音机(移动端有效)
<input type="file" accept="image/*" capture="camera">files: 已选择文件对象列表,通过 htmlInputElement.files 获取和赋值
但 <input type="file"> 包含按钮和文件列表两部分,如果给它添加宽高和背景色...
<input type="file" class="upload-inner">
<style type="text/css">
  .upload-inner {
      width: 200px;
      height: 80px;
      background-color: #e3e3e3;
  }
</style>
这个标签的样式基本没救了...
所以为了能用上更美观的上传控件,通常会选择隐藏真正的文件上传控件
然后用其他标签来代替上传按钮,在点击事件中触发上传事件

这是一个常见的上传控件样式,它的 HTML 可以这么设计:
<!-- wrapper 组件 -->
<template>
  <div class="upload-wrapper">
    <div class="upload-inputs">
      <div class="upload-bg"></div>
      <upload
        :accept="uploadConfig.accept"
      ></upload>
      <div class="tip">点击上传按钮,或拖拽文件到框内上传</div>
      <div class="tiny-tip">请选择不大于 10m 的文件</div>
    </div>
  </div>
</template>上面是外层的 <wrapper /> 组件,关于上传控件的具体逻辑可以单独封装到 <upload /> 组件中
<!-- upload 组件 -->
<template>
  <div class="file-selector">
    <button class="selector-btn" @click="handleUpClick">
      <slot>选择文件</slot>
    </button>
    <input
      ref="input"
      class="file-selector-input"
      type="file"
      :multiple="multiple"
      :accept="accept"
      @change="handleFiles"
    />
  </div>
</template>在 <upload /> 组件中,需要将 <input /> 控件隐藏掉,并用 <button> 模拟一个上传按钮样式
<style lang="scss">
.file-selector {
  margin-bottom: 16px;
  .selector-btn {
    background-color: #2976e6;
    color: #fff;
    padding: 6px 18px;
    border-radius: 4px;
    cursor: pointer;
    &:hover {
      background-color: rgba($color: #2976e6, $alpha: 0.8);
      transition: background 180ms;
    }
  }
  &-input {
    display: none;
  }
}
</style>然后通过 <button> 的上传事件来触发 <input> 的上传事件 (上面的 handleUpClick)
并监听 <input> 的 change 事件,处理所选文件 (上面的 handleFiles)
// 调用上传功能
handleUpClick() {
  this.uploadFinished // 维护一个上传状态,上传过程中禁用上传按钮
    ? this.$refs.input.click()
    : console.warn('请等待当前文件全部上传完成');
},
handleFiles(e) {
  const files = e?.target?.files;
  this.readFiles(files);
},
上面的 hanldeFiles 调用了 readFiles 方法,这是处理文件的主要逻辑
// 上传之前将文件处理为对象
readFiles(files) {
  if (!files || files.length <= 0) {
    return;
  }
  for (const file of files) {
    const url = window.URL.createObjectURL(file);
    const obj = {
      title: file.name.replace(/(\.[^\.]*$)|[\_]/gi, ''), // 去掉文件后缀
      url,
      file,
      fileType: file.fileType,
      status: 0, // 状态 -> 0 等待中,1 完成, 2 正在上传,3 上传失败
      percent: 0, // 上传进度
    };
    // 提前在 data 中定义 list,用来保存需要上传的文件
    this.list.push(obj);
  }
  // 在 data 中定义 startIndex 初始值为 0,上传完成后更新,用于追加上传文件
  this.startUpload(this.startIndex);
},然后是上传文件
// 上传前需要校验文件
checkFile(index) {
  const file = this.list[index];
  // 如果文件不存在,即全部文件上传完成
  if (!file) {
    this.uploadFinished = true;
   // 上传完成,向父组件抛出 success 事件
    this.$emit('success', this.list);
    // 清空上传控件中的值,保证 change 事件能正常触发
    this.$refs.input.value = null;
    this.startIndex = index > 1 ? index - 1 : 0;
    return false;
  }
  // 校验是否已上传
  if (`${file.status}` === "1") {
    this.startUpload(++index);
    return false;
  }
  // 校验文件大小
  if (this.maxSize && file.file && file.file.size >= this.maxSize) {
    this.startUpload(++index);
    return false;
  }
  return true;
},
// 上传单个文件
startUpload(index) {
  if (!this.checkFile(index)) { return; }
  // 开始上传,维护上传状态
  this.uploadFinished = false;
  const file = this.list[index].file;
  const fileObj = this.list[index];
  // 创建 formData 用于提交
  const data = new FormData();
  data.append('userfile', file);
  axios({
    url: this.url, // 上传接口,由 props 传入
    method: 'post',
    data,
    withCredentials: true,
    cancelToken: this.source.token, // 用于取消接口请求
    // 进度条
    onUploadProgress: e => {
      if (fileObj.status === 1) { return; } // 已上传
      // 限定最大值为 99%
      const p = parseInt((e.loaded / e.total) * 99);
      if (e.total) {
        fileObj.status = 2; // 正在上传
        fileObj.percent = p; // 更新上传进度
      } else {
        fileObj.status = 3; // 上传失败
      }
    },
  })
    .then(response => {
      if (`${response.code}` === "200") {
        fileObj.status = 1;
        fileObj.percent = 100;
      } else {
        fileObj.status = 3;
      }
    })
    .catch(e => {
      fileObj.status = 3;
    })
    .finally(e => {
      this.startUpload(++index);
    });
  // 上传完成
},上传的时候用到了 axios 的 onUploadProgress 来维护上传进度和当前文件的上传状态,用于开发上传中的样式
还使用了 cancelToken 来取消上传,不过取消上传还需要提前在 data 中定义一个 source 变量
source: axios.CancelToken.source(), // axios 取消请求所以最终 <upload /> 组件中的 data 为:
data: () => ({
    list: [], // 已选择的文件对象
    uploadFinished: true, // 上传状态
    startIndex: 0, // 开始上传的下标,用于追加文件
    source: axios.CancelToken.source(), // axios 取消请求
}),最后在组件中添加一个重置方法
reset() { // 重置
    this.list = [];
    this.source.cancel();
    this.startIndex = 0;
    this.uploadFinished = true;
    this.$refs.input && (this.$refs.input.value = null);
 },上传组件的核心逻辑就完成了
除了基本的点击按钮上传之外,还需要支持拖拽上传,这部分逻辑可以在 <wrapper /> 组件中完成
先回顾一下组件的 HTML 结构
<!-- wrapper 组件 -->
<template>
  <div class="upload-wrapper">
    <div 
      :class="['upload-inputs', dragging ? 'dragging' : '']"
      ref="pickerArea"
    >
      <div class="upload-bg"></div>
      <upload
     ref="uploadBtn"
        :accept="uploadConfig.accept"
      ></upload>
      <div class="tip">点击上传按钮,或拖拽文件到框内上传</div>
      <div class="tiny-tip">请选择不大于 10m 的文件</div>
    </div>
  </div>
</template>上面增加了一个 dragging 变量,用来控制拖拽文件时的样式,需要在 data 中定义
当组件初始化的时候,需要对 ref="pickerArea" 绑定拖拽事件
在拖拽过程中,通过维护 dragging 状态来更新热区样式,当拖拽结束后,调用 <upload /> 组件的上传功能
bindEvents() {
  const dropbox = this.$refs.pickerArea;
  // 防止重复绑定事件,需要在 data 中初始化 bindDrop 为 false
  if (!dropbox || this.bindDrop) { return }
  // 绑定拖拽事件,在组件销毁时解绑
  dropbox.addEventListener("drop", this.handleDrop, false);
  dropbox.addEventListener("dragleave", this.handleDragLeave);
  dropbox.addEventListener("dragover", this.handleDragOver);
  this.bindDrop = true;
},
// 拖拽到上传区域
handleDragOver(e) {
  e.stopPropagation();
  e.preventDefault();
  this.dragging = true;
},
// 离开上传区域
handleDragLeave(e) {
  e.stopPropagation();
  e.preventDefault();
  this.dragging = false;
},
// 拖拽结束
handleDrop(e) {
  e.stopPropagation();
  e.preventDefault();
  this.dragging = false;
  const files = e.dataTransfer.files;
 // 调用 <upload/> 组件的上传功能
  this.$refs.uploadBtn && this.$refs.uploadBtn.readFiles(files);
},上面通过 addEventLister 添加了事件监听,所以需要在 beforeDestroy 生命周期中注销
beforeDestroy() {
  // 组件销毁前解绑拖拽事件
  try {
    const dropbox = this.$refs.pickerArea;
    dropbox.removeEventListener("drop", this.handleDrop);
    dropbox.removeEventListener("dragleave", this.handleDragLeave);
    dropbox.removeEventListener("dragover", this.handleDragOver);
    this.bindDrop = false;
  } catch (e) {}
},到这里一个上传组件就完成了,不过只有上传功能,在样式和交互上还有未尽之处
比如拖拽的时候热区的样式,上传过程中拖拽按钮的禁用样式等。不过这些都维护了相应状态,通过这些状态来添加样式即可
真正没有涉及到的是已选中的文件样式,包括上传的进度条、文件操作等等
但在上面 <upload /> 组件中抛出了 this.list,其中的每个元素也维护了上传状态和上传进度,可以基于此来开发对应的交互
来自:https://www.cnblogs.com/wisewrong/archive/2020/09/01/13591315.html
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!
拖拽,拖放事件可以通过拖拽实现数据传递,达到良好的交互效果,如:从操作系统拖拽文件实现文件选择,拖拽实现元素布局的修改。原生Js实现元素拖拽、图片到指定区域进行预览的方法实现
当我们需要对Dom元素进行底层操作的时候,这时候我们就需要使用vue的自定义指令。这篇文章将讲解:如何注册Vue自定义指令?Vue的钩子函数,vue钩子函数参数,vue实现拖拽功能,实现图片加载功能,Vue自定义指令集成第三方插件 ...
移动端的拖拽有两种主流的实现方案:1. 将元素设置为固定定位,然后在拖拽的时候修改其定位,实现拖拽的效果;2. 使用 transform 中的平移 translate 属性实现拖拽。
正巧在之前面试中遇到问实现拖拽效果。当时面试的时候简单回答了实现的方式与逻辑。现在闲来无事,把这个东西实现了一下。原理很简单,写的很方便。
如果要设置物体拖拽,必须使用三个事件,分别是:1、onmousedown:鼠标按下事件2、onmousemove:鼠标移动事件3、onmouseup:鼠标抬开事件
 
 如何拖拽图片到指定位置,具体方法如下,在从本地上传图片的时候,如果使用拖拽效果,想想应该是更加的高大上,下面直接上js代码
ngDraggable.js是一款比较简单实用的angularJS拖拽插件,借助于封装好的一些自定义指令,能够快速的进行一些拖拽应用开发。首先先介绍一些基本的概念;ng-drop:是否允许放入拖拽元素
拖放是一种非常流行的用户界面模式。它的概念很简单:点击某个对象,并按住鼠标按钮不放,将鼠标移到到另一个区域,然后释放按钮将对象放到这里。
最近重读Vue官方文档,在列表的排序过渡这一小节,文档提到,<transition-group> 组件有一个特殊的地方,不仅可以实现进入和离开动画,还可以改变定位,官网示例如下
运营小姐姐说想要可以直接拖拽排序的功能,原来在序号六的广告可能会因为金主爸爸加钱换到序号一的位置,拖拽操作就很方便;实现方式:template部分、script部分
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!