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

<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 = falseevent.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 stringevent.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 = falseevent.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://xiahunao.cn/news/3016956.html

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

相关文章

【busybox记录】【shell指令】comm

目录 内容来源&#xff1a; 【GUN】【comm】指令介绍 【busybox】【comm】指令介绍 【linux】【comm】指令介绍 使用示例&#xff1a; 逐行比较两个排序后的文件 - 默认输出 逐行比较两个排序后的文件 - 如果一个文件的排序有问题&#xff0c;那么反错&#xff08;默认&…

天锐绿盾 | 办公加密系统,源代码防泄密、源代码透明加密、防止开发部门人员泄露源码

天锐绿盾作为一款专注于数据安全与防泄密的专业解决方案&#xff0c;它确实提供了针对源代码防泄密的功能&#xff0c;帮助企业保护其核心的知识产权。 PC地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是天锐绿盾可能采…

论文辅助笔记:Tempo之modules/lora.py

1 LoRALayer 基类 2 Linear 2.1 __init__ 2.2 reset_parameter & train 2.3 forward 3 MergeLinear 3.1__init__ enable_lora指定了哪些输出特征使用lora 3.2 reset_parameters & zero_pad & merge_AB 3.3 train & forward

常用算法汇总

作者&#xff1a;指针不指南吗 专栏&#xff1a;算法篇 &#x1f43e;算法思维逻辑&#x1f43e; 文章目录 1.判断闰年2.计算从某天到某天的天数3.二分4. 前缀和5.差分6.图论6.1dfs6.2走迷宫 7.最短路7.1dijkstra7.2foly 8.并查集9.数论9.1gcd lcm9.2判断素数(质数)9.3分解质因…

《Python编程从入门到实践》day21

# 昨日知识点回顾 设置背景颜色 在屏幕中央绘制飞船 # 今日知识点学习 12.5 重构&#xff1a;方法_check_events()和_update_screen() 12.5.1 方法_check_events() import sys import pygame from Settings import Settings from Ship import Shipclass AlienInvasion:"…

web 基础之 HTTP 请求

web 基础 网上冲浪 就是在互联网(internet)上获取各种信息&#xff0c;进行工作&#xff0c;或者娱乐&#xff0c;他的英文表示surfing the Internet&#xff0c;因 “surfing”d的意思是冲浪&#xff0c;即成为网上冲浪&#xff0c;这是一种形象说法&#xff0c; 也是一个非…

快速排序找出第K大的元素

有序数组里第 K 大的元素就是index 为 array.length - k 的元素。 快速排序的思路主要就是选一个基准值p&#xff0c;然后将小于p的值放在p的左右&#xff0c;大于p的值放在p的右边&#xff0c;然后对左右数组进行递归。 利用这个思路&#xff0c;当我们找到这个基准值对应的 i…

2024.05.07作业

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//窗口相关设置this->resize(540,415);this->setFixedSize(540,415);//窗口标题this->setWindowTitle…

LeetCode 面试经典150题 252.会议室

题目&#xff1a;给定一个会议时间安排的数组 intervals &#xff0c;每个会议时间都会包括开始和结束的时间 intervals[i] [starti, endi] &#xff0c;请你判断一个人是否能够参加这里面的全部会议。 思路&#xff1a;因为一个人在同一时刻只能参加一个会议&#xff0c;因此…

牛客 | 字符金字塔

请打印输出一个字符金字塔&#xff0c;字符金字塔的特征请参考样例 #include <stdio.h> #include <string.h> using namespace std; int main() {char c;scanf("%c", &c);for (int i 1; i < (c - 64); i)//第一个循环决定了有多少行{//c:67 第三…

如何自己快速的制作流程图?6个软件教你快速进行流程图制作

如何自己快速的制作流程图&#xff1f;6个软件教你快速进行流程图制作 自己制作流程图可以是项目管理、流程设计或教学展示中的重要环节。以下是六款常用的流程图制作软件&#xff0c;它们都提供了快速、简单的方式来制作流程图&#xff1a; 迅捷画图&#xff1a;这是一款非…

PMBOK第七版,通往项目管理的新地图|分析2024软考光环PMP第六版培训课程

目录 文明福利 历次升级分析 2PMBOK第七版解读 1、和第六版保持一致&#xff1a;由知识体系指南和项目管理标准2部分构成。 2、区别于第六版的结构性颠覆&#xff1a;12原则、8大绩效域取代5大过程组、10大知识领域。 3PMBOK第七版VS第六版 4PMBOK第七版 就是带领我们寻找…

力扣刷题--数组--第二天

今天仍然做二分查找相关的题目。先来回顾一下二分查找的方法和使用的条件。二分查找是在数组中查找目标值的一种方法&#xff0c;通过边界索引确定中间索引&#xff0c;判断中间索引处的元素值和目标值的大小&#xff0c;来不断缩小查找区间。使用二分查找有如下一些限制&#…

Map集合的实现类~TreeMap

重复依据&#xff1a;通过对键进行排序 先创建Student类&#xff0c;并在主函数new对象&#xff0c;然后创建TreeMap&#xff1a; 建立红黑树&#xff0c;需要在Student类后面实现类的接口&#xff1a; 重写其中的compareTo方法&#xff1a; 或者可以自定义比较器&#xff1a; …

AndroidStudio的Iguana版的使用

1.AndroidStudio介绍 Android Studio 是用于开发 Android 应用的官方集成开发环境 (IDE)。Android Studio 基于 IntelliJ IDEA 强大的代码编辑器和开发者工具&#xff0c;还提供更多可提高 Android 应用构建效率的功能&#xff0c;例如&#xff1a; 基于 Gradle 的灵活构建系统…

基于springboot+vue+Mysql的教师人事档案管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

[redis] 说一说 redis 的底层数据结构

Redis有动态字符串(sds)、链表(list)、字典(ht)、跳跃表(skiplist)、整数集合(intset)、压缩列表(ziplist) 等底层数据结构。 Redis并没有使用这些数据结构来直接实现键值对数据库&#xff0c;而是基于这些数据结构创建了一个对象系统&#xff0c;来表示所有的key-value。 文章…

CRC校验原理及步骤

文章目录 CRC定义&#xff1a;CRC校验原理&#xff1a;CRC校验步骤&#xff1a; CRC定义&#xff1a; CRC即循环冗余校验码&#xff0c;是数据通信领域中最常用的一种查错校验码&#xff0c;其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查&#xff08;CRC&#…

Pycharm远程同步的mapping与sync

用Pycharm进行项目远程部署的时候会遇到两个同步文件&#xff0c;一个是点击 tools—>deployment—>configration——>mapping 一个是链接虚拟环境的时候会有一个sync&#xff0c;那么这两种同步有什么区别呢&#xff1f; 区别就是&#xff0c;2包括1&#xff0c;要用…

运维实施工程师之Linux服务器全套教程

一、Linux目录结构 1.1 基本介绍 Linux 的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录“/”&#xff0c;然后在此目录下再创建其他的目录。 在 Linux 世界里&#xff0c;一切皆文件&#xff08;即使是一个硬件设备&#xff0c;也是使用文本来标…