vue3+ts 原生 js drag drop 实现

news2024/12/24 0:11:17

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 为需要被拖动的元素。

<div class="source-item" draggable="true"></div>                 

在这里插入图片描述

三、最终要实现的结果

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

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 传递数据的。

<div class="source-item"
     :data="item"
     :ondragstart="dragstart"
     :ondragend="dragend"
     draggable="true"
     v-for="item in sourceArray" :key="item">
    <span>item-{{item}}</span>
</div>
/**
 * 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() 里是看不到的,但它是在里面的,后面会从这个事件里提取。先看一下是怎样的,这个后面会具体说:

// 设置数据
event.dataTransfer!.setData('text/plain', data)

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

2. 释放区域的事件

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

  • ondragover 当拖动元素悬于释放区域时,这个事件是连续触发的,每动一个像素都会被触发。
  • ondragenter 当拖动元素进入释放区域时,触发一次,不会连续触发
  • ondragleave 当拖动元素离开释放区域时,触发一次,不会连续触发
  • ondrop 当拖动元素在释放区域释放时,触发一次
<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>
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

<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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1638857.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud整合Gateway结合Nacos

目录 一、引入依赖 二、开启两个测试项目 2.1 order service ​编辑 2.2 user service 三、gateway项目 3.1 新建一个bootstrap.yml文件 3.2 将我们的的网关配置写道nacos里的配置里 3.3 测试&#xff1a;看能够根据网关路由到两个测试的项目 四、 优化 4.1 将项目打包…

数据库(MySQL)—— DQL语句(聚合,分组,排序,分页)

数据库&#xff08;MySQL&#xff09;—— DQL语句&#xff08;聚合&#xff0c;分组&#xff0c;排序&#xff0c;分页&#xff09; 聚合函数常见的聚合函数语法 分组查询语法 排序查询语法 分页查询语法 DQL的执行顺序 我们今天来继续学习MySQL的DQL语句的聚合和分组查询&…

【面试经典 150 | 数组】接雨水

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;预处理方法二&#xff1a;单调栈方法三&#xff1a;双指针 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff…

【大模型学习】Transformer(学习笔记)

Transformer介绍 word2vec Word2Vec是一种用于将词语映射到连续向量空间的技术&#xff0c;它是由Google的Tomas Mikolov等人开发的。Word2Vec模型通过学习大量文本数据中的词语上下文信息&#xff0c;将每个词语表示为高维空间中的向量。在这个向量空间中&#xff0c;具有相似…

【C++】学习笔记——string_2

文章目录 六、string类2. 反向迭代器const迭代器 string类对象的容量操作&#xff08;补&#xff09;size() 3. string类的元素访问4. string类的修改 未完待续 结合文档食用~ 六、string类 2. 反向迭代器 一般来说&#xff0c;迭代器都是正向的遍历容器&#xff0c;虽然可以…

LuaJIT源码分析(三)字符串

LuaJIT源码分析&#xff08;三&#xff09;字符串 要表示一个字符串&#xff0c;核心就是需要知道字符串的长度&#xff0c;以及存放字符串具体数据的地址。lua的字符串是内化不可变的&#xff0c;也就是lua字符串变量存放的不是字符串的拷贝&#xff0c;而是字符串的引用。那么…

C语言⼆级指针如何操作字符串数组(指针数组)?

一、问题 对于字符串数组该如何操作&#xff08;⽽且是使⽤指针数组存储&#xff09;&#xff1f; 二、解答 使⽤指针的指针实现对字符串数组中字符串的输出。指向指针的指针即是指向指针数据的指针变量。这⾥创建⼀个指针数组 strings&#xff0c;它的每个数组元素相当于⼀个…

springcloud自定义全局异常

自行创建一个实体类 /*** 全局异常处理类**/ ControllerAdvice public class GlobalExceptionHandler {ExceptionHandler(Exception.class) ResponseBody public Result error(Exception e){e.printStackTrace(); return Result.fail();}/*** 自定义异常处理方法* param e * re…

神奇的Vue3 - 组件探索

神奇的Vue3 第一章 神奇的Vue3—基础篇 第二章 神奇的Vue3—Pinia 文章目录 神奇的Vue3了解组件一、注册组件1. 全局注册​2. 局部注册3. 组件命名 二、属性详解1. Props&#xff08;1&#xff09;基础使用方法&#xff08;2&#xff09;数据流向&#xff1a;单项绑定原则&…

深入图像分类:使用美国手语数据集训练定制化神经网络

引言 在前一篇博客中&#xff0c;我们探讨了如何使用MNIST数据集训练一个基础的神经网络来进行手写数字识别。在本文中&#xff0c;我们将更进一步&#xff0c;使用美国手语字母表&#xff08;ASL&#xff09;数据集来构建一个定制化的图像分类模型。通过这个过程&#xff0c;…

YOLOv9/YOLOv8算法改进【NO.128】 使用ICCV2023超轻量级且高效的动态上采样器( DySample)改进yolov8中的上采样

前 言 YOLO算法改进系列出到这&#xff0c;很多朋友问改进如何选择是最佳的&#xff0c;下面我就根据个人多年的写作发文章以及指导发文章的经验来看&#xff0c;按照优先顺序进行排序讲解YOLO算法改进方法的顺序选择。具体有需求的同学可以私信我沟通&#xff1a; 首推…

静态库、动态库回顾

回顾一下库相关的知识点&#xff0c;总结备忘一下。在某种情况下&#xff0c;你有了如下的代码&#xff0c;结构如下 //pra.h #include <stdio.h> void test_01(); //pra.c #include "pra.h" void test_01() {printf("xxxxxxx----->%s %s()\n",…

Docker 安装部署 postgres

Docker 安装部署 postgres 1、拉取 postgres 镜像文件 [rootiZbp19a67kznq0h0rgosuxZ ~]# docker pull postgres:latest latest: Pulling from library/postgres b0a0cf830b12: Pull complete dda3d8fbd5ed: Pull complete 283a477db7bb: Pull complete 91d2729fa4d5: Pul…

C#知识|汇总方法重载与静态方法应用技巧

哈喽&#xff0c;你好&#xff0c;我是雷工&#xff01; 今天学习C#方法重载与静态方法应用技巧的相关内容。 01 方法重载有什么好处&#xff1f; 1.1、可以有效的减少类的对外接口&#xff08;只显示一个方法比较简洁&#xff09;&#xff0c;从而降低类的复杂度。 1.2、方便…

ubuntu开启message文件

环境&#xff1a;ubuntu 20.04 1、首先需要修改 /etc/rsyslog.d/50-default.conf 文件&#xff1b;源文件中message被注释&#xff0c;如下图&#xff1a; 2、打开注释&#xff1a; 3、重启服务 systemctl restart rsyslog.service 如此即可&#xff01;

freeRTOS事件标志组(1-16)

def&#xff1a;事件标志组简介 事件标志位&#xff1a;用一个为来表示事件是否发生 事件标志组是事件标志位的集合可以简单的理解事件标志组&#xff0c;就是一个整数 事件标志组的特点: 1&#xff1a;它的每一个位表示一个事件&#xff08;高8位不算&#xff09; 2&#…

分布式与一致性协议之Raft算法与一致哈希算法(一)

Raft算法 Raft与一致性 有很多人把Raft算法当成一致性算法&#xff0c;其实它不是一致性算法而是共识算法&#xff0c;是一个Multi-Paxos算法&#xff0c;实现的是如何就一系列值达成共识。并且&#xff0c;Raft算法能容忍少数节点的故障。虽然Raft算法能实现强一致性&#x…

打印机-STM32版本 硬件部分

最终PCB EDA工程: 一、确定芯片型号 根据项目需求&#xff0c;梳理需要用到的功能&#xff0c; 电量检测&#xff1a;ADC 按键&#xff1a;IO input外部中断 LED&#xff1a;IO output 温度检测&#xff1a;ADC 电机控制&#xff1a;IO output 打印通讯&#xff1a;SPI …

HarmaonyOS鸿蒙应用科普课

一、什么是鸿蒙OS&#xff1f; 1.概念&#xff1a; 先给大家讲讲今天讲课的主题&#xff0c;鸿蒙OS是什么&#xff1f;鸿蒙系统大家都知道&#xff0c;就是一个操作系统&#xff0c;我们未来是为的成为鸿蒙程序员。所以我们不要将鸿蒙os完全等同于手机操作系统&#xff0c;太…

笔记:编写程序,绘制一个展示支付宝月账单报告的饼图,

文章目录 前言一、饼图是什么&#xff1f;二、分析题目三、编写代码总结 前言 编写程序&#xff0c;绘制一个展示支付宝月账单报告的饼图&#xff0c;实现过程如下&#xff1a; &#xff08;1&#xff09; 导入 matplotlib.pyplot 模块&#xff1b; &#xff08;2&#xff09;…