前端拖拽相关功能详解,一篇文章总结前端关于拖拽的应用场景和实现方式(含 ...

打印 上一主题 下一主题

主题 948|帖子 948|积分 2844

前言

本篇文章所有的代码,都是在 vue + vite + ts 项目底子之上实现的,这样也是为了方便大家直接用源码,在开始之前发起大家阅读这篇《零底子搭建 vite项 目教程》。此项目就是这个教程搭建的,本篇文章关于拖拽的相关代码是此项目的一个分支。假如你没有时间阅读详细的教程,你也可以直接在 git 上克隆项目。
   learn-vite: w搭建简朴的vite+ts+vue框架
  本篇文章的所有代码都在 drag 分支上,根本内容大致如下(后续代码可能会有优化):

把项目克隆之后切换到 drag 分支,运行 npm run dev 直接访问 http://localhost:80/drag  就可以看到本篇文章涉及的全部内容。
我将拖拽的功能大致分为五大类,分别是(1)拖拽上传、(2)拖拽调解宽度、(3)拖拽调解按钮位置、(4)拖拽排序、(5)拖拽将文件移动到文件夹。这些都是比力常用的,除此之外实在还有许多复杂的情况,后续会再优化和补充。

一、文件拖拽到指定区域上传

文件拖拽上传,是一个很底子的功能,一般来说我们有两种选择,(1)利用组件库,(2)利用本身封装的方法。
1.1 组件库实现拖拽上传

常见的组件库,好比 elementPlus 、antDesignVue 等都提供上传组件。

利用组件库的好处是,简朴、安装即用,组件库一般都提供很成熟的方法和各种变乱的回调,免得我们再自界说。组件库的拖拽上传本质上就是利用了 js 的 drag、drop 等变乱来实现的。
但是组件库的缺点也显而易见:
(1)会影响页面的 html 结构

由于我们在利用的时候,必要在页面增加一个 elUpload 标签,然后页面上所有的内容都必要包裹在这个 elUpload 标签内,页面的结构和结构都受这个标签的影响。
(2)样式必要自界说

组件库的样式许多时候不是我们想要的,还必要对它的样式举行修改自界说,徒增工作量。
   我认为修改组件库的样式是一件很麻烦的事情,由于组件库的样式过于全面,对于许多操纵好比 hover、focus、active 都有对应的样式。但是实际上我们的需求根本不用考虑这么多,所以在利用组件库的时候,样式覆盖的要考虑得很全面,还要考虑样式的层级关系,动不动就必要加一些 !important ,也很容易出现题目。
  
  所以我在实际开辟过程中,假如是小功能能不用组件库就不用,好比一个简朴的输入框,我选择自界说 input 标签,而不是利用 el-input。
  
  但是有些时候还是要用的,好比一个 popover 功能,我们必要它自动定位的时候就直接用 el-popover 比力好,由于计算它的位置是一个很麻烦的事情,也就是人们常说的不要本身造轮子。
  
  总之,能找到对于本身来说更高效的开辟方法就好。
  对于一些新增的功能还好,可以利用组件库进步工作效率;但是假如在一个现有的旧页面中,增加上传功能,就不推荐利用组件库,由于我们最好不要改变页面原来的 dom 结构。
解决办法就是我们自界说一个拖拽上传方法。
(3)代码

我的这个项目用的组件库是 ant-design-vue ,但是文档中是用 elementPlus 举例的,实际上都是大同小异。
  1. <template>
  2.         <div class="drag-upload-container">
  3.                 <h1 class="title">使用组件库实现拖拽上传文件</h1>
  4.                 <!-- 使用组件库实现拖拽上传,需要在模版中增加一个标签,入下面的 a-upload-draager -->
  5.                 <a-upload-dragger v-model:fileList="fileList" name="file" :multiple="true" action="https://www.mocky.io/v2/5cc8019d300000980a055e76" @change="handleChange" @drop="handleDrop">
  6.                         <p class="ant-upload-text">Click or drag file to this area to upload</p>
  7.                         <p class="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
  8.                 </a-upload-dragger>
  9.         </div>
  10. </template>
  11. <script lang="ts" setup>
  12. import { UploadDragger as AUploadDragger } from 'ant-design-vue'
  13. import { ref } from 'vue'
  14. const fileList = ref([])
  15. const handleChange = () => {
  16.         //
  17. }
  18. const handleDrop = () => {
  19.         //
  20. }
  21. </script>
  22. <style lang="scss" scoped>
  23. .drag-upload-container {
  24.         padding: 24px;
  25.         .title {
  26.                 margin: 10px 0 20px;
  27.         }
  28. }
  29. </style>
复制代码
  本小结的源代码在项目中的 src/pages/drag/dragUpload.vue  文件夹中。
  1.2 本身封装拖拽上传方法

为相识决 1.1 节组件库影响 dom 结构的题目,我们必要本身封装一个拖拽上传方法,宗旨是:

  • 岂论页面结构多复杂,都不要影响原来的 dom 结构。
  • 一个封装的公共类,复制到任何项目都可以利用
我们要知道拖拽的本质就是利用了 javascript 的 drag/drop 等变乱,开始之前耐心的看一下 mdn 官方关于 HTML 拖拽 API 的介绍。HTML 的拖拽 API
   拖放的主要步骤是 drop 变乱界说的一个释放区(释放文件的目的元素)和为 dragover 变乱界说一个变乱处理程序。
  (1)根本步骤


  • 界说拖放区域,用于触发 drop 变乱
  • 给拖放区域界说 drapover 变乱和样式,用于指示用户此处可以拖放文件
  • 对 drop 变乱的详细界说,文件的获取和处理
我们在封装类的时候,必要把拖放区域的 dom 当作一个参数传入,这样就可以在任何地方复用。必要用到的所有变乱有:

  • dragenter: 拖拽进入【拖放区域】,此时可能要展示某些提示文案或样式
  • dragover: 在【拖放区域】中,此时可能要展示某些提示文案或样式
  • dragleave: 拖拽脱离【拖放区域】,此时可能要隐藏某些提示文案或样式
  • drop: 在【拖放区域】松开鼠标,此时要获取我们拖拽的文件,并举行后续的处理
   除了上面 4 个变乱,还有 drag、 dragstart 、dragend 三个变乱是我们在当前功能用不到的,由于这三个变乱是针对【被拖拽元素】的,拖拽上传功能中,【被拖拽元素】是我们电脑文件系统中的某个文件。
  
  假如【被拖拽元素】是我们当前页面的某个 dom 元素,那么这几个变乱就有用了,在本篇文章的第五章【拖拽将文件移动到文件夹】中有用到。
  (2)利用 dataTransfer 传递数据

   DataTransfer 接口是一个 HTML 原生接口,用于保存拖动并放下过程中的数据,他可以保存一项或多项数据,这些数据项可以是一种或者多种数据范例
  我们只必要在 drop 变乱中取 event.dataTransfer.files 就可以获取到我们正在拖拽的文件
  1. // 一个 drop 事件
  2. function onDrop(evt: DragEvent) {
  3.     // 获取到拖拽的文件
  4.         const res = evt.dataTransfer?.files
  5. }
复制代码
(3)变乱监听

还有一个紧张的题目,我们在监听 drop、dragover 等变乱的时候,应该是监听我们的【拖放区域】元素的变乱,而不是 document 等。在本例中,我们将【拖放区域】元素作为一个参数传递给封装的类,这样功能就可以复用了。
(4)代码

下面是我封装的一个 DragUploader 类,在这个类中,我们把拖拽中的【提示文案 tipText】通过参数传入,并在类的内部创建并设置对应的 dom 元素的样式(tipEle)。实际上,我们也可以直接把拖拽中的样式的整个 dom 作为参数传入,看具体需求和开辟习惯。
  1. export class DragUploader {
  2.         // 拖拽区域的父元素
  3.         el: HTMLElement | null = null
  4.         // 拖拽中的文案
  5.         tipText: string = ''
  6.         // 拖拽中展示的元素
  7.         tipEle: HTMLElement | null = null
  8.         // 拖拽上传文件之后的回调
  9.         fileCb: (files: Array<File>) => void
  10.         constructor({ el, tipText, fileCb }: { el: HTMLElement; tipText: string; fileCb: (Files: Array<File>) => void }) {
  11.                 this.el = el
  12.                 this.tipText = tipText
  13.                 // 创建拖拽中展示的元素
  14.                 this.tipEle = this.createTipEle(tipText)
  15.                 // 获取文件后的回调,以供后续使用
  16.                 this.fileCb = fileCb
  17.                 // 监听拖拽事件,创建实例的时候就开始监听
  18.                 this.addListener(el)
  19.         }
  20.         createTipEle(tipText: string) {
  21.                 const ele = document.createElement('div')
  22.                 // 自定义样式,使用绝对定位,需要设置根元素的 position
  23.                 ele.style.position = 'absolute'
  24.                 ele.style.top = '0px'
  25.                 ele.style.left = '0px'
  26.                 ele.style.display = 'none'
  27.                 ele.style.alignItems = 'center'
  28.                 ele.style.justifyContent = 'center'
  29.                 ele.style.width = '100%'
  30.                 ele.style.height = '100%'
  31.                 ele.style.background = '#fff'
  32.                 ele.style.pointerEvents = 'none' // 必须加上这句,否则会重复触发 drag事件
  33.                 ele.innerHTML = tipText
  34.                 return ele
  35.         }
  36.         // 显示拖拽中的样式元素
  37.         showTipEle() {
  38.                 if (!this.tipEle) {
  39.                         return
  40.                 }
  41.                 this.tipEle.style.display = 'inline-flex'
  42.         }
  43.         //隐藏拖拽中的样式元素
  44.         hideTipEle() {
  45.                 if (!this.tipEle) {
  46.                         return
  47.                 }
  48.                 this.tipEle.style.display = 'none'
  49.         }
  50.         onDragover(evt: DragEvent) {
  51.                 // 一定要阻止默认事件,否则会调用浏览器的默认行为,打开拖拽的图片或者可预览的文件
  52.                 evt.stopPropagation()
  53.                 evt.preventDefault()
  54.                 this.showTipEle()
  55.         }
  56.         onDragEnter(evt: DragEvent) {
  57.                 evt.stopPropagation()
  58.                 evt.preventDefault()
  59.                 this.showTipEle()
  60.         }
  61.         onDragLeave(evt: DragEvent) {
  62.                 evt.stopPropagation()
  63.                 evt.preventDefault()
  64.                 this.hideTipEle()
  65.         }
  66.         onDrop(evt: DragEvent) {
  67.                 evt.stopPropagation()
  68.                 evt.preventDefault()
  69.                 this.hideTipEle()
  70.                 const res = (evt.dataTransfer?.files || []) as Array<File>
  71.                 this.fileCb(res)
  72.         }
  73.         // 一定要绑定 this 指针
  74.         handleDragOver = this.onDragover.bind(this)
  75.         handleDragEnter = this.onDragEnter.bind(this)
  76.         handleDragLeave = this.onDragLeave.bind(this)
  77.         handleDrop = this.onDrop.bind(this)
  78.         addListener(el: HTMLElement) {
  79.                 // 避免重复插入拖拽中的样式元素
  80.                 if (el.contains(this.tipEle)) {
  81.                         return
  82.                 }
  83.                 // 将拖拽中的样式插入根元素
  84.                 if (this.tipEle) {
  85.                         el.appendChild(this.tipEle)
  86.                 }
  87.                 el.addEventListener('dragover', this.handleDragOver, false)
  88.                 el.addEventListener('dragenter', this.handleDragEnter, false)
  89.                 el.addEventListener('dragleave', this.handleDragLeave, false)
  90.                 el.addEventListener('drop', this.handleDrop, false)
  91.         }
  92.         // 移除事件监听,实例调用的,一般在组件的卸载的时候调用
  93.         removeListenner(): void {
  94.                 if (!this.el) {
  95.                         return
  96.                 }
  97.                 this.el.removeEventListener('dragover', this.handleDragOver, true)
  98.                 this.el.removeEventListener('dragenter', this.handleDragEnter, false)
  99.                 this.el.removeEventListener('dragleave', this.handleDragLeave, true)
  100.                 this.el.removeEventListener('drop', this.handleDrop, true)
  101.         }
  102. }
复制代码
在 vue 项目中利用
  1. <template>
  2.         <div class="drag-upload-container">
  3.                 <h1 class="title">自己封装上传组件</h1>
  4.                 <h2>需求:拖拽到下面虚线区域上传文件,不要使用组件库</h2>
  5.                 <ul ref="dragEle" class="target-drag-area">
  6.                         <li>床前明月光</li>
  7.                         <li>疑是地上霜</li>
  8.                         <li>举头望明月</li>
  9.                         <li>低头思故乡</li>
  10.                 </ul>
  11.         </div>
  12. </template>
  13. <script lang="ts" setup>
  14. import { onBeforeUnmount, onMounted, ref, Ref } from 'vue'
  15. import { DragUploader } from './dragUpload'
  16. const dragEle = ref(null)
  17. const dragInstance: Ref<DragUploader | null> = ref(null)
  18. const initDragger = () => {
  19.         if (!dragEle.value) {
  20.                 return
  21.         }
  22.         dragInstance.value = new DragUploader({
  23.                 el: dragEle.value,
  24.                 tipText: '拖拽到此处上传文件',
  25.                 fileCb: (files: Array<File>) => {
  26.                         // 在这里可以获取拖拽的文件
  27.                         // 获取到了拖拽的文件,后续的操作就随意了
  28.                         console.log(111, files)
  29.                 },
  30.         })
  31. }
  32. onMounted(() => {
  33.         initDragger()
  34. })
  35. onBeforeUnmount(() => {
  36.         if (dragInstance.value) {
  37.                 dragInstance.value.removeListenner()
  38.         }
  39. })
  40. </script>
  41. <style lang="scss" scoped>
  42. .drag-upload-container {
  43.         padding: 24px;
  44.         .title {
  45.                 margin: 10px 0;
  46.         }
  47.         .target-drag-area {
  48.                 margin-top: 10px;
  49.                 padding: 10px;
  50.                 border: 1px dashed;
  51.                 border-radius: 10px;
  52.                 position: relative;
  53.                 overflow: hidden;
  54.         }
  55. }
  56. </style>
复制代码
  本小结的源代码在项目中的 src/pages/drag/dragUploadCustom.vue  文件夹中。
  1.3 拖拽上传总结 

我们可以在看一下 elementPlus 中拖拽上传组件是怎样实现的。在 elementPlus 官方克隆源代码,找到 upload 文件夹,找到 upload-dragger.vue 文件,我们可以发现本质都是一样的,由于这是一个很简朴的功能。
换句话说,假如你不用 el-upload 也不用 1.2 末节的方法,你也可以本身封装一个 my-upload 组件,然后利用 drop、dragover 等变乱实现你的拖拽上传,这样的好处是可以自界说样式,不用修改组件库的复杂样式,弊端一样是对于复杂的旧项目会影响 dom 结构。

二、拖拽调解分屏宽度

拖拽调解分屏的宽度是一个非常常见又紧张的功能,现在各种 AI 网站层出不穷,你会发现险些每个产物都有分屏功能,左侧预览文件,右侧是 AI 对话,然后鼠标拖拽可以实现两侧分屏宽度的调解,页面根本结构如下:

需求概括:

  • 页面 Wrap 分为左右两个区域 A 和 B
  • A 和 B 中间有一个可以拖拽的竖线 C,用鼠标拖拽 C 在 Wrap 内左右移动,可以根据 C 的位置调解 A 和 B 的宽度。
实现方法实在也是利用 js 的各种变乱,但是它和上传文件差别,虽然字面上都是拖拽,但是这个功能利用的是鼠标变乱,mousedown,mousemove,mouseup 等。
在开始之前,同样可以在 MDN 上复习一下关于鼠标变乱的官方文档,MouseEvent,我们用到的变乱有

  • mousedown:拖拽元素 C 的鼠标按下变乱,代表开始拖拽
  • mousemove:可拖拽区域 Wrap 的鼠标移动变乱,代表拖拽中
  • mouseup: 可拖拽区域 Wrap 的鼠标放开变乱,代表竣事拖拽
  • mouseleave: 可拖拽区域 Wrap 的鼠标脱离变乱,代表竣事拖拽
   鼠标变乱中除了上述变乱之外,还有 mouseenter、mouseleave、mouseout、mousewheel 变乱是这个功能不会用到的。
  注意到我们利用的这四个变乱,是针对两个 dom 举行监听的,一个是拖拽元素 C ,一个是可拖拽区域的元素 Wrap。
2.1 手动计算分屏宽度

(1)根本步骤

假设我们有一个拖拽的分割线元素 C、一个可拖拽区域元素 Wrap,要实现手动计算分屏宽度,根本步骤如下:

  • 监听 C 元素的 mousedown 变乱,代表开始拖拽
  • 监听 Wrap 元素的 mousemove 变乱,代表正在拖拽中
  • 监听 Wrap 元素的 mouseup 变乱,代表竣事拖拽
  • 监听 Wrap 元素的 mouseleave 变乱,代表竣事拖拽(这是在处理边沿情况)
关于我们必须要监听 mouseleave 变乱,是由于许多时候我们会鼠标一直按下然后移出可拖拽区域才放开,这个时候是不会触发 Wrap 的 mouseup 变乱的,会导致一直处于拖拽中的情况,所以我们可以在 mouseleave 变乱中把拖拽的状态设置为 false,表示已经竣事拖拽了。
(2)代码

下面是利用 vue 实现的简朴的调解分屏宽度的代码,这样写简朴需求没什么题目,但是你会发现在 initDrag 里面有许多变乱监听的代码。假如我们在许多页面都必要实现分屏的功能,这部门代码就必要复制许多遍,这很不优雅,所以我们完全可以把监听变乱的方法封装成一个公共的类。
  1. <template>
  2.         <div class="drag-split-screen">
  3.                 <h1 class="title">拖拽调整 A B区域的宽度</h1>
  4.                 <p>是否在拖拽中: {
  5.   
  6.   { isDragging }}</p>
  7.                 <p>左侧 A 区域的宽度: {
  8.   
  9.   { leftWidth }}</p>
  10.                 <div ref="dragParent" class="container">
  11.                         <!-- 区域 A 的宽度指定 -->
  12.                         <div class="area area-a" :style="{ width: `${leftWidth}px` }">区域 A</div>
  13.                         <div ref="dragEle" class="drag"></div>
  14.                         <!-- 区域 B 的宽度自适应 -->
  15.                         <div class="area area-b">区域 B</div>
  16.                 </div>
  17.         </div>
  18. </template>
  19. <script lang="ts" setup>
  20. import { Ref, onMounted, ref } from 'vue'
  21. const leftWidth = ref(200)
  22. // 拖拽区域的父元素
  23. const dragParent: Ref<HTMLElement | null> = ref(null)
  24. const dragEle: Ref<HTMLElement | null> = ref(null)
  25. const isDragging = ref(false)
  26. const initDrag = () => {
  27.         if (!dragEle.value || !dragParent.value) {
  28.                 return
  29.         }
  30.         // 区域 A 的左侧边缘,距离屏幕最右侧的偏移量
  31.         const offsetX = dragParent.value.getBoundingClientRect().left
  32.         // 监听拖拽元素的 mousedown事件,代表拖拽的开始
  33.         dragEle.value.onmousedown = (evt: MouseEvent) => {
  34.                 evt.stopPropagation()
  35.                 evt.preventDefault()
  36.                 isDragging.value = true
  37.         }
  38.         // 监听 父元素 dragParent 的 mousemove 事件,代表拖拽中
  39.         // 注意,监听的是 父元素 的而不是拖拽元素的,因为监听不到
  40.         // 其实也可以监听 document,主要看需求
  41.         // mousemove 可以增加 debounce 防抖
  42.         dragParent.value.onmousemove = (evt: MouseEvent) => {
  43.                 evt.stopPropagation()
  44.                 evt.preventDefault()
  45.                 if (!isDragging.value) {
  46.                         return
  47.                 }
  48.                 //鼠标事件的 evt.x 鼠标当前是距离页面左边的距离
  49.                 leftWidth.value = evt.x - offsetX
  50.                 // 这里面还可以限制宽度的最小最大值
  51.                 // ...
  52.         }
  53.         // 监听document 的 mouseup 事件,代表拖拽结束
  54.         dragParent.value.onmouseup = evt => {
  55.                 evt.stopPropagation()
  56.                 evt.preventDefault()
  57.                 isDragging.value = false
  58.         }
  59.         dragParent.value.onmouseleave = function (evt) {
  60.                 evt.stopPropagation()
  61.                 evt.preventDefault()
  62.                 isDragging.value = false
  63.         }
  64. }
  65. onMounted(() => {
  66.         initDrag()
  67. })
  68. </script>
  69. <style lang="scss" scoped>
  70. .drag-split-screen {
  71.         padding: 24px 24px;
  72.         .title {
  73.                 margin: 10px 0;
  74.         }
  75.         .container {
  76.                 width: 100%;
  77.                 height: 300px;
  78.                 display: flex;
  79.                 align-items: stretch;
  80.                 justify-content: center;
  81.                 border: 1px solid;
  82.                 .area {
  83.                         display: inline-flex;
  84.                         align-items: center;
  85.                         justify-content: center;
  86.                         width: 100%;
  87.                         &.area-a {
  88.                                 flex-shrink: 0;
  89.                         }
  90.                         &.area-b {
  91.                                 flex-grow: 1;
  92.                         }
  93.                 }
  94.                 .drag {
  95.                         width: 4px;
  96.                         height: 100%;
  97.                         flex-shrink: 0;
  98.                         background: #000;
  99.                         cursor: ew-resize;
  100.                 }
  101.         }
  102. }
  103. </style>
复制代码
2.2 本身封装分屏方法

本身封装一个分屏方法作为对 2.1 方法的优化,本质和原理都是一样的,只是可以进步代码的可复用性能。
(1)根本步骤


  • 界说一个公共类
  • 将拖拽元素 dragEle 以参数的形式传入
  • 将可拖拽区域元素 parentEle 以参数的形式传入
  • 支持拖拽开始、拖拽中、拖拽竣事变乱回调
(2)代码

  1. export class DragSplitScreen {
  2.         dragEle: HTMLElement | null = null
  3.         parentEle: HTMLElement | null = null
  4.         // 拖拽的回调函数
  5.         dragStart?: (left: number) => void
  6.         dragMove?: (left: number) => void
  7.         dragEnd?: () => void
  8.         constructor({
  9.                 dragEle,
  10.                 parentEle,
  11.                 dragStart,
  12.                 dragMove,
  13.                 dragEnd,
  14.         }: {
  15.                 dragEle: HTMLElement
  16.                 parentEle?: HTMLElement
  17.                 dragStart?: (left: number) => void
  18.                 dragMove?: (left: number) => void
  19.                 dragEnd?: () => void
  20.         }) {
  21.                 // 如果不指定父元素,默认使用 document.body
  22.                 this.parentEle = parentEle || document.body
  23.                 // 拖拽的元素
  24.                 this.dragEle = dragEle
  25.                 this.addListener()
  26.                 this.dragStart = dragStart
  27.                 this.dragMove = dragMove
  28.                 this.dragEnd = dragEnd
  29.         }
  30.         onDragStart(evt: MouseEvent) {
  31.                 evt.stopPropagation()
  32.                 if (this.dragStart) {
  33.                         this.dragStart(evt.x)
  34.                 }
  35.                 if (!this.parentEle) {
  36.                         return
  37.                 }
  38.                 this.parentEle.addEventListener('mousemove', this.handleDragMove, true)
  39.                 this.parentEle.addEventListener('mouseup', this.handleDragEnd, true)
  40.         }
  41.         onDragMove(evt: MouseEvent) {
  42.                 evt.stopPropagation()
  43.                 if (this.dragMove) {
  44.                         this.dragMove(evt.x)
  45.                 }
  46.         }
  47.         onDragEnd(evt: MouseEvent) {
  48.                 evt.stopPropagation()
  49.                 if (this.dragEnd) {
  50.                         this.dragEnd()
  51.                 }
  52.                 if (!this.parentEle) {
  53.                         return
  54.                 }
  55.                 this.parentEle.removeEventListener('mousemove', this.handleDragMove, true)
  56.         }
  57.         handleDragStart = this.onDragStart.bind(this)
  58.         handleDragMove = this.onDragMove.bind(this)
  59.         handleDragEnd = this.onDragEnd.bind(this)
  60.         addListener() {
  61.                 if (!this.dragEle || !this.parentEle) {
  62.                         return
  63.                 }
  64.                 this.dragEle.addEventListener('mousedown', this.handleDragStart, true)
  65.         }
  66. }
复制代码
在 vue 中利用,我们可以发现相比于 2.1 末节,我们的 initDrag 方法中的代码量明显淘汰了,变乱监听的方法都在类中,无需我们额外的处理。
  1. <template>
  2.         <div class="drag-split-screen">
  3.                 <h1 class="title">封装成公共方法,拖拽调整 A B区域的宽度</h1>
  4.                 <p>是否在拖拽中: {
  5.   
  6.   { isDragging }}</p>
  7.                 <p>左侧 A 区域的宽度: {
  8.   
  9.   { leftWidth }}</p>
  10.                 <div ref="dragParent" class="container">
  11.                         <!-- 区域 A 的宽度指定 -->
  12.                         <div class="area area-a" :style="{ width: `${leftWidth}px` }">区域 A</div>
  13.                         <div ref="dragEle" class="drag"></div>
  14.                         <!-- 区域 B 的宽度自适应 -->
  15.                         <div class="area area-b">区域 B</div>
  16.                 </div>
  17.         </div>
  18. </template>
  19. <script lang="ts" setup>
  20. import { Ref, onMounted, ref } from 'vue'
  21. import { DragSplitScreen } from './dragSplitScreen'
  22. const leftWidth = ref(200)
  23. // 拖拽区域的父元素
  24. const dragParent: Ref<HTMLElement | null> = ref(null)
  25. const dragEle: Ref<HTMLElement | null> = ref(null)
  26. const isDragging = ref(false)
  27. const dragInstance: Ref<DragSplitScreen | null> = ref(null)
  28. const initDrag = () => {
  29.         if (!dragEle.value || !dragParent.value) {
  30.                 return
  31.         }
  32.         // 区域 A 的左侧边缘,距离屏幕最右侧的偏移量
  33.         const offsetX = dragParent.value.getBoundingClientRect().left
  34.         dragInstance.value = new DragSplitScreen({
  35.                 dragEle: dragEle.value,
  36.                 parentEle: dragParent.value,
  37.                 dragStart: (left: number) => {
  38.                         isDragging.value = true
  39.                 },
  40.                 dragMove: (left: number) => {
  41.                         leftWidth.value = left - offsetX
  42.                 },
  43.                 dragEnd: () => {
  44.                         isDragging.value = false
  45.                 },
  46.         })
  47. }
  48. onMounted(() => {
  49.         initDrag()
  50. })
  51. </script>
  52. <style lang="scss" scoped>
  53. .drag-split-screen {
  54.         padding: 24px 24px;
  55.         .title {
  56.                 margin: 10px 0;
  57.         }
  58.         .container {
  59.                 width: 100%;
  60.                 height: 300px;
  61.                 display: flex;
  62.                 align-items: stretch;
  63.                 justify-content: center;
  64.                 border: 1px solid;
  65.                 .area {
  66.                         display: inline-flex;
  67.                         align-items: center;
  68.                         justify-content: center;
  69.                         width: 100%;
  70.                         &.area-a {
  71.                                 flex-shrink: 0;
  72.                         }
  73.                         &.area-b {
  74.                                 flex-grow: 1;
  75.                         }
  76.                 }
  77.                 .drag {
  78.                         width: 4px;
  79.                         height: 100%;
  80.                         flex-shrink: 0;
  81.                         background: #000;
  82.                         cursor: ew-resize;
  83.                 }
  84.         }
  85. }
  86. </style>
复制代码
  本小结的源代码在项目中的 src/pages/drag/dragSplitScreenCustom.vue 文件夹中。
  2.3 拖拽分屏总结

在拖拽调解分屏宽度的需求中,我们还有一些细节必要注意,好比拖拽中鼠标样式。
一般来说在拖拽中我们必要给拖拽元素设置 cursor: col-resize 或者 cursor: ew-resize,如下的样式

   
     指示双向重新设置大小    我们不但要给拖拽的元素 C 设置这个样式,在拖拽中,我们还必要给可拖拽区域 Wrap 设置成这个样式,然后在拖拽竣事给 Wrap 重置。
三、悬浮按钮拖动移动位置

悬浮按钮,就是指 position: fixed / absolute 的元素,一些网站的置顶按钮、紧张操纵按钮都是这样的,这些按钮有的可以在屏幕内随意拖动并放置,有的只可以上下拖动改变位置,这也是一个很通例的功能。
3.1 悬浮按钮拖拽停在屏幕的恣意位置

停在屏幕的恣意位置,分析这是一个被设置为 postition: fixed 的按钮,实现方式类似第 2 章的方法,利用鼠标的 mousedown + mousemove + mouseup + mouseleave 变乱,同样必要监听两个元素,一个是悬浮按钮,一个是 document.body 。
(1)代码

看起来很简朴,由于在这个需求中我们的可拖拽区域是整个屏幕,所以根本不必要考虑一些位置上的偏移。
  1. <template>
  2.         <div class="drag-page-container">
  3.                 <h1 class="title">悬浮按钮拖拽停在屏幕的任意位置</h1>
  4.                 <h2>正在拖拽: {
  5.   
  6.   { isDragging }}</h2>
  7.                 <div ref="fixedBtnEle" class="fix-btn" :style="{ left: `${style.left}px`, top: `${style.top}px` }">drag</div>
  8.         </div>
  9. </template>
  10. <script lang="ts" setup>
  11. import { Ref, onMounted, ref } from 'vue'
  12. const fixedBtnEle: Ref<HTMLElement | null> = ref(null)
  13. const isDragging = ref(false)
  14. const style = ref({
  15.         left: 100,
  16.         top: 100,
  17. })
  18. const initDrag = () => {
  19.         if (!fixedBtnEle.value) {
  20.                 return
  21.         }
  22.         let offsetX = 0
  23.         let offsetY = 0
  24.         fixedBtnEle.value.addEventListener('mousedown', evt => {
  25.                 isDragging.value = true
  26.                 // 鼠标开始移动的时候,相对于 drag 元素的边界的偏移量
  27.                 offsetX = evt.x - style.value.left
  28.                 offsetY = evt.y - style.value.top
  29.         })
  30.         document.body.addEventListener('mousemove', evt => {
  31.                 if (!isDragging.value) {
  32.                         return
  33.                 }
  34.                 // 在这里可以做一些边界值的处理,如:判断是否超出屏幕边界等
  35.                 style.value = {
  36.                         left: evt.x - offsetX,
  37.                         top: evt.y - offsetY,
  38.                 }
  39.         })
  40.         document.body.addEventListener('mouseup', () => {
  41.                 isDragging.value = false
  42.         })
  43.         // 需要加一个 mouseleave 事件,否则当鼠标移动到页面外部之后不会触发 mouseup
  44.         // 导致再移回来的时候位置还会随鼠标变化
  45.         document.body.addEventListener('mouseleave', () => {
  46.                 isDragging.value = false
  47.         })
  48. }
  49. onMounted(() => {
  50.         initDrag()
  51. })
  52. </script>
  53. <style lang="scss" scoped>
  54. .drag-page-container {
  55.         position: relative;
  56.         padding: 24px;
  57.         .title {
  58.                 margin: 10px 0;
  59.         }
  60.         .fix-btn {
  61.                 display: inline-flex;
  62.                 align-items: center;
  63.                 justify-content: center;
  64.                 width: 50px;
  65.                 height: 50px;
  66.                 border-radius: 50%;
  67.                 border: 1px solid;
  68.                 background: red;
  69.                 color: #ffff;
  70.                 cursor: pointer;
  71.                 position: fixed;
  72.                 left: 100px;
  73.                 top: 100px;
  74.                 user-select: none;
  75.         }
  76. }
  77. </style>
复制代码
  本小结的源代码在项目中的 src/pages/drag/dragFixedBtn.vue 文件夹中。
  3.2 悬浮按钮只能在指定区域移动

相对更复杂也是更常见的需求是让一个按钮只能在指定区域移动,好比只能上下移动,这个时候我们利用同样的方法,然后再考虑一下可拖拽区域的边沿情况,和鼠标位置的偏移量即可。
(1)代码

在这种场景中,悬浮按钮是相对于可拖拽区域绝对定位的,所以计算位置的时候要减去拖拽区域的位置偏移,利用 getBoundingClientRect  方法获取可拖拽区域元素的偏移量。
假如要求只能上下移动,那我们把可拖拽区域的宽度设置为和按钮宽度一样就行,只能程度移动同理。
  1. <template>
  2.         <div class="drag-page-container">
  3.                 <h1 class="title">悬浮按钮,只能下面虚线区域内移动</h1>
  4.                 <h2>正在拖拽: {
  5.   
  6.   { isDragging }}</h2>
  7.                 <div ref="dragAreaEle" class="drag-area">
  8.                         <div ref="fixedBtnEle" class="fix-btn" :style="{ left: `${style.left}px`, top: `${style.top}px` }">drag</div>
  9.                 </div>
  10.         </div>
  11. </template>
  12. <script lang="ts" setup>
  13. import { Ref, onMounted, ref } from 'vue'
  14. const dragAreaEle: Ref<HTMLElement | null> = ref(null)
  15. const fixedBtnEle: Ref<HTMLElement | null> = ref(null)
  16. const isDragging = ref(false)
  17. const style = ref({
  18.         left: 0,
  19.         top: 0,
  20. })
  21. const initDrag = () => {
  22.         if (!fixedBtnEle.value || !dragAreaEle.value) {
  23.                 return
  24.         }
  25.         // 区域的边界
  26.         const boundary = dragAreaEle.value.getBoundingClientRect()
  27.         let minX = boundary.left
  28.         let maxX = boundary.right
  29.         // 悬浮按钮的宽高
  30.         let fixedBtnWidth = fixedBtnEle.value?.clientWidth || 0
  31.         let fixedBtnHeight = fixedBtnEle.value?.clientHeight || 0
  32.         // 相对于 drag 元素的边界的偏移量
  33.         let offsetX = 0
  34.         let offsetY = 0
  35.         fixedBtnEle.value.addEventListener('mousedown', evt => {
  36.                 isDragging.value = true
  37.                 // 鼠标开始移动的时候,相对于 drag 元素的边界的偏移量
  38.                 offsetX = evt.x - style.value.left - boundary.left
  39.                 offsetY = evt.y - style.value.top - boundary.top
  40.         })
  41.         document.body.addEventListener('mousemove', evt => {
  42.                 if (!isDragging.value) {
  43.                         return
  44.                 }
  45.                 // 在这里可以做一些边界值的处理,如:判断是否超出屏幕边界等
  46.                 let targetLeft = evt.x - offsetX - boundary.left
  47.                 let targetTop = evt.y - offsetY - boundary.top
  48.                 // 限制不能超出区域边界
  49.                 targetLeft = Math.max(0, targetLeft)
  50.                 targetLeft = Math.min(boundary.width - fixedBtnWidth, targetLeft)
  51.                 targetTop = Math.max(0, targetTop)
  52.                 targetTop = Math.min(boundary.height - fixedBtnHeight, targetTop)
  53.                 // 这是悬浮按钮相对于区域的绝对定位的 left 和 top
  54.                 style.value = {
  55.                         left: targetLeft,
  56.                         top: targetTop,
  57.                 }
  58.         })
  59.         document.body.addEventListener('mouseup', () => {
  60.                 isDragging.value = false
  61.         })
  62.         // 需要加一个 mouseleave 事件,否则当鼠标移动到页面外部之后不会触发 mouseup
  63.         // 导致再移回来的时候位置还会随鼠标变化
  64.         document.body.addEventListener('mouseleave', () => {
  65.                 isDragging.value = false
  66.         })
  67. }
  68. onMounted(() => {
  69.         initDrag()
  70. })
  71. </script>
  72. <style lang="scss" scoped>
  73. .drag-page-container {
  74.         position: relative;
  75.         padding: 24px;
  76.         .title {
  77.                 margin: 10px 0;
  78.         }
  79.         .drag-area {
  80.                 margin-top: 40px;
  81.                 width: 80vw;
  82.                 height: 200px;
  83.                 border-radius: 4px;
  84.                 border: 1px dashed;
  85.                 position: relative;
  86.         }
  87.         .fix-btn {
  88.                 display: inline-flex;
  89.                 align-items: center;
  90.                 justify-content: center;
  91.                 width: 50px;
  92.                 height: 50px;
  93.                 border-radius: 50%;
  94.                 background: red;
  95.                 color: #ffff;
  96.                 cursor: pointer;
  97.                 position: absolute;
  98.                 left: 100px;
  99.                 top: 100px;
  100.                 user-select: none;
  101.         }
  102. }
  103. </style>
复制代码
  本小结的源代码在项目中的 src/pages/drag/dragFixedBtnArea.vue 文件夹中
  四、拖拽排序

拖拽排序包罗按钮、菜单、文件等许多东西,这是个十分复杂的功能,由于我们不但要处理各种位置计算,还必要增加动画使整个拖拽举动更加优雅。假如让我本身写,那可费劲了,好在我们有现成的库,vue 有一个 vuedraggable 的npm包,非常好用。
   Vue.Draggable 是一款基于 Sortable.js 实现的 vue拖拽插件
  拖拽排序功能,可以简朴,也可以复杂,好比浏览器的标签页就是可以拖拽排序的,要做到这么优雅还是必要费点劲的。还有一些文档软件中,每个文档的顺序也是可以拖拽排序的,它不但要实现功能,还要增加许多效果,好比指示可以放置位置的样式等,本篇文章只实现简朴的功能,也就是 vuedraggable 这个包的根本利用方法。
4.1 根本功能

下图概括了利用 vuedraggable 由简朴到复杂的情况,可以本身克隆代码然后看下效果

4.2 代码

  1. <template>
  2.         <div class="container">
  3.                 <h1>拖拽调整按钮的顺序</h1>
  4.                 <h2>使用 vuedraggable npm包,安装 npm i vuedraggable@next 【这个是vue3版本】</h2>
  5.                 <h2>1. 简单粗暴的版本</h2>
  6.                 <ul>
  7.                         <li>没有动画效果,拖拽中的时候页面上有两个正在拖拽中的元素</li>
  8.                 </ul>
  9.                 <draggable v-model="menuList" class="drag-box" item-key="id" :force-fallback="true">
  10.                         <template #item="{ element }">
  11.                                 <div class="btn-item" :style="{ background: element.bgColor }">{
  12.   
  13.   { element.name }}</div>
  14.                         </template>
  15.                 </draggable>
  16.                 <h2>2. 增加动画效果</h2>
  17.                 <ul>
  18.                         <li>增加动画效果</li>
  19.                         <li>使用 animation 属性</li>
  20.                 </ul>
  21.                 <draggable v-model="menuList" class="drag-box" item-key="id" animation="200" :force-fallback="true">
  22.                         <template #item="{ element }">
  23.                                 <div class="btn-item" :style="{ background: element?.bgColor || '' }">{
  24.   
  25.   { element.name }}</div>
  26.                         </template>
  27.                 </draggable>
  28.                 <h2>3. 隐藏拖拽中元素的占位元素</h2>
  29.                 <ul>
  30.                         <li>使用 ghost-class 给拖拽元素的占位符增加 class,并设置透明度为 0 ,看起来就像隐藏了一样,还可以增加更多样式</li>
  31.                 </ul>
  32.                 <draggable v-model="menuList" class="drag-box" ghost-class="drag-ghost" item-key="id" animation="200" :force-fallback="true">
  33.                         <template #item="{ element }">
  34.                                 <div class="btn-item" :style="{ background: element?.bgColor || '' }">{
  35.   
  36.   { element.name }}</div>
  37.                         </template>
  38.                 </draggable>
  39.                 <h2>4. 隐藏拖拽中的元素</h2>
  40.                 <ul>
  41.                         <li>使用 drag-chosen 给拖拽中元素的增加 class, 并设置透明度为0,看起来就像隐藏了一样</li>
  42.                         <li>下面的效果,看起来像是只能在一个固定的虚线区域拖拽</li>
  43.                         <li>适用场景:类似浏览器多个标签页拖拽调整顺序</li>
  44.                 </ul>
  45.                 <draggable v-model="menuList" class="drag-box" :move="onDragMove" chosen-class="drag-chosen" item-key="id" animation="200" :force-fallback="true">
  46.                         <template #item="{ element }">
  47.                                 <div class="btn-item" :style="{ background: element?.bgColor || '' }">{
  48.   
  49.   { element.name }}</div>
  50.                         </template>
  51.                 </draggable>
  52.         </div>
  53. </template>
  54. <script lang="ts" setup>
  55. import { onMounted, ref } from 'vue'
  56. import draggable from 'vuedraggable'
  57. const menuList = ref([
  58.         {
  59.                 id: 1,
  60.                 name: '菜单一',
  61.                 bgColor: 'red', // 为了更好的看效果,每个项目加一个单独的背景色
  62.         },
  63.         {
  64.                 id: 2,
  65.                 name: '菜单二',
  66.                 bgColor: 'pink',
  67.         },
  68.         {
  69.                 id: 3,
  70.                 name: '菜单三',
  71.                 bgColor: 'green',
  72.         },
  73.         {
  74.                 id: 4,
  75.                 name: '菜单四',
  76.                 bgColor: 'blue',
  77.         },
  78. ])
  79. onMounted(() => {})
  80. </script>
  81. <style lang="scss" scoped>
  82. .container {
  83.         padding: 24px;
  84.         h1 {
  85.                 margin: 10px 0;
  86.                 font-size: 20px;
  87.         }
  88.         h2 {
  89.                 margin: 20px 0 10px;
  90.                 font-size: 18px;
  91.         }
  92.         ul {
  93.                 padding-left: 20px;
  94.                 list-style: decimal;
  95.         }
  96.         .drag-box {
  97.                 display: inline-flex;
  98.                 align-items: center;
  99.                 justify-content: flex-start;
  100.                 flex-wrap: wrap;
  101.                 width: 100%;
  102.                 user-select: none;
  103.                 border: 1px dashed;
  104.                 padding: 10px;
  105.                 position: relative;
  106.                 .btn-item {
  107.                         display: inline-flex;
  108.                         align-items: center;
  109.                         justify-content: center;
  110.                         margin: 0 10px 10px 0;
  111.                         width: 100px;
  112.                         height: 40px;
  113.                         color: #fff;
  114.                         border-radius: 10px;
  115.                         cursor: pointer;
  116.                         &.drag-ghost {
  117.                                 opacity: 0; // 视觉上隐藏拖拽中的元素的占位元素,可以给占位符元素增加各种样式
  118.                         }
  119.                         &.drag-chosen.sortable-drag {
  120.                                 opacity: 0 !important; // 视觉上隐藏拖拽中的元素,可以给拖拽中的元素增加各种样式
  121.                         }
  122.                 }
  123.         }
  124. }
  125. </style>
复制代码
  本小结的源代码在项目中的 src/pages/drag/dragSort.vue 文件夹中。
  五、拖拽将文件移动到文件夹

虽然在第四章中我们有一个比力好用的包 vuedraggable 来处理我们的拖拽,但是它不是万能的,它的主要功能还是在拖拽 + 排序。
我们之前提到过文件系同一般有一个文件列表拖拽排序的功能,实在还有另一个功能就是“拖拽将文件移动到文件夹”,文件列表中不但有文件,还可能有文件夹,许多产物都支持直接拖拽文件把它拖到一个文件夹中,这个需求中有几个紧张的点:

  • 只有文件可以拖拽,文件夹不可以拖拽
  • 拖拽中,只有文件夹可以放置
可以克隆项目,然后看一下这个功能的简朴版本

5.1 根本思路

要实现这个功能,我们必要用到和第一章“拖拽”上传类似的变乱,实在实现方法也是类似,区别在于,“拖拽”上传,我们的【可拖拽元素】是位于电脑系统中的,所以我们没有用到 drag、dragstart、dragend变乱,这一点我们之前也提到过。
但是本节的功能却用到了这三个变乱,由于本节功能的【可拖拽元素】是在同一个页面上的 dom。本节用到的所有变乱如下

  • dragstart:针对“文件”
  • dragend:针对“文件”
  • dragover:针对“文件夹”
  • drop:针对“文件夹”
   此外还有 dragenter、dragleave 实在也可以用到,作为一些细节的优化点,但是我这里没有用到
  5.2 利用 dataTransfer 传递数据

在 1.2 中我们只在 drop 变乱中利用 dataTransfer 获取数据,但是我们实在还可以在 drag 或者 dragstart 变乱中设置我们要传递的数据,然后再在 drop 变乱中获取值。
  1. const onDragStart = (evt: DragEvent, item) => {
  2.         console.log('dragstart', evt, item)
  3.         if (!evt.target) {
  4.                 return
  5.         }
  6.         // 获取拖拽中的元素 id
  7.         dragEleId.value = (evt.target as HTMLElement).getAttribute('data-id')
  8.         // 可以通过 dataTransfer 传值给 drop 事件
  9.         evt.dataTransfer?.setData('drag-id', `拖拽ID-${dragEleId.value}`)
  10. }
复制代码
5.3 代码

下面的这些代码,实在并不是很美满,还有许多题目没有解决,好比拖拽中的文件、文件夹的样式怎么处理,我想在文件列表中隐藏拖拽中的“文件”,给它设置  opacity: 0 或者 display: none 都没有乐成,都会影响拖拽变乱。这个背面还得再看一下,该怎么处理,或者谁有解决办法,告诉我一下~~感谢~
  1. <template>        <div class="container">                <h1>拖拽将文件移动到文件夹</h1>                <ul>                        <li>将文件拖拽,移动到文件夹中</li>                        <li>拖拽中,将拖拽的元素在列表中设置特殊样式</li>                        <li>拖拽中,除了拖拽中的元素,其他文件置灰</li>                        <li>拖拽中,可放置的文件夹显示可放置的样式</li>                </ul>                <!-- 必须设置 dragover 或者dragenter才能 触发drop -->                <div class="file-box">                        <div                                v-for="item in fileList"                                :key="item.id"                                class="file-item"                                :data-id="item.id"                                :class="[                                        item.type,                                        { 'is-draging': item.id == dragEleId },                                        {                                                'can-drop': item.type === 'folder' && dragEleId,                                        },                                        {                                                'can-not-drop': item.type !== 'folder' && dragEleId,                                        },                                ]"                                :draggable="item.type !== 'folder'"                                @dragstart="onDragStart($event, item)"                                @dragend="onDragEnd"                                @dragover="onDragOver($event, item)"                                @drop="onDrop($event, item)"                        >                                <FileTextOutlined v-if="item.type == 'file'" />                                <FolderOutlined v-else />                                <div class="text">{    { item.id }}-- {    { item.name }}</div>                        </div>                </div>        </div></template><script lang="ts" setup>import { FolderOutlined, FileTextOutlined } from '@ant-design/icons-vue'import { onMounted, ref } from 'vue'const fileList = ref([        {                id: 1,                type: 'file',                name: '怎样学好前端',        },        {                id: 2,                type: 'folder',                name: '工具',        },        {                id: 3,                type: 'folder',                name: '聚集',        },        {                id: 4,                type: 'file',                name: '怎么摸鱼',        },])const dragEleId = ref()// 利用 dragstart 变乱,不用 drag 变乱,由于前者只触发一次,drag变乱是持续触发的const onDragStart = (evt: DragEvent, item) => {
  2.         console.log('dragstart', evt, item)
  3.         if (!evt.target) {
  4.                 return
  5.         }
  6.         // 获取拖拽中的元素 id
  7.         dragEleId.value = (evt.target as HTMLElement).getAttribute('data-id')
  8.         // 可以通过 dataTransfer 传值给 drop 事件
  9.         evt.dataTransfer?.setData('drag-id', `拖拽ID-${dragEleId.value}`)
  10. }const onDragEnd = (evt: DragEvent) => {        dragEleId.value = null        console.log('dragend')}// 必须给可以放置的元素,即“文件夹”,设置 dargover 变乱,并且阻止默认变乱,才会触发 drop 变乱const onDragOver = (evt: DragEvent, item: any) => {        // drag over 必须设置 preventDefault,才能触发 drop 变乱        evt.preventDefault()        console.log('dragover', evt)}const onDrop = (evt: DragEvent, item: any) => {        // 在 drop 变乱的 dataTransfer 中担当值        const data = evt.dataTransfer?.getData('drag-id')        console.log('ondrop', data)        if (item.type !== 'folder') {                dragEleId.value = null                return        }        // 移除拖拽的元素,假装它已经放进了文件夹中,实际应用中可能调用接口之类的        const targetIndex = fileList.value.findIndex(item => Number(item.id) === Number(dragEleId.value))        if (targetIndex >= 0) {                fileList.value.splice(targetIndex, 1)        }        dragEleId.value = null}</script><style lang="scss" scoped>.container {        padding: 24px;        user-select: none;        h1 {                margin: 10px 0;                font-size: 20px;        }        h2 {                margin: 20px 0 10px;                font-size: 18px;        }        ul {                list-style: decimal;                margin-left: 20px;        }        .file-box {                width: 100%;                height: 200px;                border: 1px solid #ccc;                border-radius: 4px;                .file-item + .file-item {                        margin-top: 5px;                }                .file-item {                        display: flex;                        align-items: center;                        justify-content: flex-start;                        padding: 10px;                        height: unset;                        cursor: pointer;                        &.can-drop {                                border: 1px dashed red; // 可放置的(文件夹)样式                        }                        &.can-not-drop {                                opacity: 0.5; // 不可放置的(文件范例)的样式                                background: #fafafa;                        }                        &.is-draging {                                // opacity: 0; // 不能设置透明度为0,否则鼠标下面也没有拖拽的元素了,可以本身试一下                                opacity: 0.3;                                color: blue;                                // display: none; // 不能设置拖拽中的元素不展示,否则不能通过 dataTransfer传值,而且直接就出发dragend变乱了                        }                        .text {                                margin-left: 10px;                        }                }        }}</style>
复制代码
总结

本篇文章总结了前端开辟过程中常见的 5 种关于拖拽的功能,根本涵盖了简朴的系统必要的拖拽场景,背面假如有复杂的拖拽功能我会继续补充,文章内容很长,感谢您的耐心。
文章源代码在 git 上可直接下载
learn-vite: 搭建简朴的vite+ts+vue框架
https://gitee.com/yangjihong2113/learn-vite
感谢大家阅读,欢迎关注,我们一起学习进步,我会持续更新前端开辟相关的系统化的教程,新手发起关注我的系统化专栏《前端工程化系统教程》所有教程都包含源码,内容持续更新中渴望对你有资助。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表