vue3+ts 原生 js drag drop 实现

vue3+ts 原生 js drag drop 实现

一直以来没有涉及的一个领域就是 drag drop 拖动操作,研究了下,实现了,所以写个教程。

官方说明页面及实例:https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API

最终效果:

一、拖动的 html 结构

比如我要将右侧的元素拖动到左边,这里 html 有几个组成部分。

  1. 左边绿色的 .target 目标
  2. 右侧蓝色的 .source 源
  3. 右侧内部的 .source-item 待拖动元素

二、标记元素可被拖动

要想拖动某个元素,需要标记这个元素为可拖动元素,如果不标记,在拖动 某个元素的时候,鼠标上并不会实时跟随被拖动的元素到鼠标指针下。

要想实现这样,只需要添加 draggable 属性即可,这个例子里,上面的多个 .source-item 为需要被拖动的元素。

html 复制代码
<div class="source-item" draggable="true"></div>                 

三、最终要实现的结果

这个例子里,我定义了两个数组,一个源数组,一个目标数组。

ts 复制代码
const sourceArray = ref<Array<number>>([1,2,3,4,5,6])
const targetArray = ref<Array<number>>([])

拖动要实现的功能是,拖动时,将元素从 源数组 中移动到 目标数组 中。

四、拖动过程、对应的事件

这个过程中被拖动元素释放区域 都需要绑定相应的事件,才能完成整个拖动过程。整个过程需要完成好多个事件的响应。

拖动事件是这样进行的:

  1. 拖动元素,(给事件添加绑定这个拖动行为的数据)
  2. 拖到目标区域上方,释放。(获取当前拖动事件的数据,进行下一步操作)

1. 被拖动元素需要响应的事件

被拖动元素需要响应的事件有:

  • ondragstart 拖动开始,在元素被拖动时触发。在这个事件里添加当前对应拖动行为的数据,比如被拖动元素的 Index 等需要的数据
  • ondragend 拖动结束,在元素被释放时触发。这个是取消拖动的操作,本例不作操作。

本例中我在被拖动元素中添加了 data="数据" 的属性,这个在 ondragstart 的时候取用里面的数据,并设置到 .dataTransfer 中。

被拖动元素和最终释放到的元素之间是通过这个 event.dataTransfer 传递数据的。

html 复制代码
<div class="source-item"
     :data="item"
     :ondragstart="dragstart"
     :ondragend="dragend"
     draggable="true"
     v-for="item in sourceArray" :key="item">
    <span>item-{{item}}</span>
</div>
js 复制代码
/**
 * Drag Item
 */
function dragstart(event: DragEvent){
    let data = (event.target as HTMLElement).getAttribute('data') as string  // 获取 html 里的 data 属性
    event.dataTransfer!.dropEffect = 'copy'   // copy | move | link 不知道干嘛的,好像也没什么效果
    event.dataTransfer!.setData('text/plain', data)  // 添加数据
    console.log('item-drag-start: ')
}
function dragend(event: DragEvent){
    console.log('item-drag-end')
}

这样,关于被拖动元素需要设置的东西就是这些了。

这里先了解一下这个 DragEvent 里都有什么:

上面在 dataTransfer 里添加的数据,在 console.log() 里是看不到的,但它是在里面的,后面会从这个事件里提取。先看一下是怎样的,这个后面会具体说:

ts 复制代码
// 设置数据
event.dataTransfer!.setData('text/plain', data)

// 提取数据
const originalData = Number(event.dataTransfer!.getData("text/plain"))

2. 释放区域的事件

拖动释放区域,也就是接收区域 div.target 需要实现的事件是:

  • ondragover 当拖动元素悬于释放区域时,这个事件是连续触发的,每动一个像素都会被触发。
  • ondragenter 当拖动元素进入释放区域时,触发一次,不会连续触发
  • ondragleave 当拖动元素离开释放区域时,触发一次,不会连续触发
  • ondrop 当拖动元素在释放区域释放时,触发一次
html 复制代码
<div
    :class="['target', {'is-drag-entered': isDragEntered}]"
    :ondragover="onTargetDragover"
    :ondragenter="onTargetDragenter"
    :ondragleave="onTargetDragLeave"
    :ondrop="handleDrop"
>
    <div class="source-item"
         v-for="item in targetArray" :key="item">
        <span>item-{{item}}</span>
    </div>
</div>
ts 复制代码
const refTargetZone = ref()
onMounted(()=>{
    nextTick(()=>{
        refTargetZone.value.addEventListener('drop', handleDrop)
        // ondrop 的事件需要以这样的方式添加,直接写到 html 中不生效,不知道为什么
    })
})


/**
 * Drag Target
 */

const isDragEntered = ref(false)  // 实现拖动元素进入释放区域时,改变释放区域的样式,就是为了给个操作反馈

function onTargetDragover(event: DragEvent){
    event.preventDefault()  // 这里就特别注意,这行很关键
}
function onTargetDragenter(event: DragEvent){
    console.log('drag-enter: ')
    isDragEntered.value = true
}
function onTargetDragLeave(event: DragEvent){
    console.log('drag-enter: ',)
    isDragEntered.value = false
}

// 释放拖动的元素到目标区域时
function handleDrop(event: DragEvent){
    isDragEntered.value = false

    event.preventDefault() // 这里就特别注意,这行很关键
    const originalData = Number(event.dataTransfer!.getData("text/plain")) // 取事件中的数据,这个数据是拖动开始时设置的。

    // 数据变化
    // 因为 vue 是数据驱动的,这里只需要操作 源、目标 数据,即可实现页面上界面的变化,
    // 不需要像原生 dom 那样去操作 dom 来实现拖动的变化。
    targetArray.value.push(originalData) // 目标数组添加对应值
    sourceArray.value = sourceArray.value.filter(item => item !== originalData) // 源数组删除对应值

    console.log('--- on drop:', originalData)
}

五、更进一步

上面的例子里传递的是普通字符串,它也可以传递文件什么的,看官方具体是如何操作的。

拖动到某个序列的某个位置,可能就需要对事件的坐标位置进行进一步判断了。

官方说明页面及实例:https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API

六、完整代码

Drag.vue

html 复制代码
<template>
    <div class="drag-container">
        <el-row :gutter="50">
            <el-col :span="12">
                <div
                    :class="['target', {'is-drag-entered': isDragEntered}]"
                    :ondragover="onTargetDragover"
                    :ondragenter="onTargetDragenter"
                    :ondragleave="onTargetDragLeave"
                    :ondrop="handleDrop"
                >
                    <div class="source-item"
                         v-for="item in targetArray" :key="item">
                        <span>item-{{item}}</span>
                    </div>
                </div>
            </el-col>
            <el-col :span="12">
                <div class="source">
                    <div class="source-item"
                         :data="item"
                        :ondragstart="dragstart"
                        :ondragend="dragend"
                        draggable="true"
                        v-for="item in sourceArray" :key="item">
                        <span>item-{{item}}</span>
                    </div>
                </div>
            </el-col>
        </el-row>
    </div>
</template>
<script setup lang="ts">
import {ref} from "vue";

const isDragEntered = ref(false)

const sourceArray = ref<Array<number>>([1,2,3,4,5,6])
const targetArray = ref<Array<number>>([])

/**
 * Drag Item
 */
function dragstart(event: DragEvent){
    let data = (event.target as HTMLElement).getAttribute('data') as string
    event.dataTransfer!.dropEffect = 'move'
    event.dataTransfer!.setData('text/plain', data)
    console.log('item-drag-start:' ,event)
}
function dragend(event: DragEvent){
    console.log('item-drag-end')
}

function handleDrop(event: DragEvent){
    isDragEntered.value = false

    event.preventDefault()
    const originalData = Number(event.dataTransfer!.getData("text/plain"))

    // 数据变化
    targetArray.value.push(originalData)
    sourceArray.value = sourceArray.value.filter(item => item !== originalData)

    console.log('--- on drop:', originalData)
}



/**
 * Drag Target
 */
function onTargetDragover(event: DragEvent){
    event.preventDefault()
    // console.log('drag-over: ', event)
}
function onTargetDragenter(event: DragEvent){
    console.log('drag-enter: ')
    isDragEntered.value = true
}

function onTargetDragLeave(event: DragEvent){
    console.log('drag-enter: ',)
    isDragEntered.value = false
}




</script>

<style scoped lang="scss">
.drag-container{
    padding: 30px;
}
.source, .target{
    padding: 20px;
    height: 400px;
    display: flex;
    flex-flow: row wrap;
    -webkit-border-radius: 20px;
    -moz-border-radius: 20px;
    border-radius: 20px;
    border: 2px solid #007AFF;
    background-color: white;
    &:hover{
        border-style: dashed;
    }
    .source-item{
        background-color: white;
        display: flex;
        align-items: center;
        justify-content: center;

        text-transform: uppercase;
        height: 60px;
        width: 100px;
        margin-bottom: 5px;
        margin-right: 5px;
        padding: 10px;
        text-align: center;
        border: 2px solid black;
        &:hover{
            background-color: #4CD964;
            cursor: pointer;
            user-select: none;
        }
    }
}

.source{

}
.target{
    border-color: #4CD964;
    &.is-drag-entered{
        background-color: #4CD964;
    }

}
</style>
相关推荐
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功1 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程1 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
cs_dn_Jie2 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic3 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js