【记】Vue - 拖拽元素组件实现

article/2025/5/12 10:29:56

需求描述:

1.可实现PC/移动端元素拖拽移动
2.支持2种模式:1.元素跟随光标点放置2.元素在光标点平齐位置靠侧边吸附

市面上估计有很多这种组件和功能了,但我没找到合适的,用了VueUse的useDraggable Function感觉不太适合某些应用场景(比如需要拖拽的点击button),故自己手动实现了一下,此次实现也算是对事件处理以及元素定位的相关属性有了比较深入的了解了,仅以本文记录一下。也欢迎大佬们批评指正。

实现思路:

整体思路

1.组件元素包括三部分:1.移动容器2.可拖拽元素3.操作元素

移动容器包裹可拖拽元素和操作元素,且可拖拽元素和操作元素在页面中二者只显示其一。 当props.snapEdge === false时,可拖拽元素和操作元素为同一个,通过default slot传入; 当props.snapEdge === true时,可拖拽元素为snapEdge slot传入的元素,操作元素为default slot传入的元素。

2.拖拽可拖拽元素,可以放置整个移动容器的位置,支持2种方式:1.在光标所在处放置容器2.在光标所在平齐处放置元素靠侧边吸附

两种方式切换通过props.snapEdge设置。

细节思路

1.DragEvent实现PC端拖拽功能

PC端拖拽可通过DragEvent事件监听(ondragstart、ondragend)【为什么不用MouseEvent(onmousedown、onmousemove、onmouseup、……),主要考虑是为了防止和内部元素的click事件冲突。vueuse中的useDraggable Function就存在这个问题,useDraggable Function源码中是通过PointerEvent事件监听,而PointerEvent是继承自MouseEvent,对其源码感兴趣的可转以上链接】 <img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c1d9a6374e4c49209a6dd307d3513479~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image “摘自useDraggable Function源码”) 在drag事件执行过程中会判断2个因素” style=“margin: auto” />

1.可拖拽元素:可拖拽元素通过draggable属性设置;
2.可放置的目标元素:默认情况下,浏览器会阻止在大多数 HTML 元素上放置某些内容时发生任何事情。要想目标元素变为可放置元素,该元素需要通过ondragover事件来阻止默认事件的发生。

即通过对拖拽元素本身和其父元素中添加ondragover事件

const prevent = (evt: DragEvent) => {evt.preventDefault();evt.dataTransfer.dropEffect = 'move'
};
dragContainerRef.value.addEventListener('dragover', prevent);
dragContainerRef.value.parentNode.addEventListener('dragover', prevent); 

2.TouchEvent实现移动端拖拽功能

移动端拖拽可通过TouchEvent事件监听(ontouchstart、ontouchmove、ontouchend)

3.元素随光标移动实现

在按下元素后记录鼠标相对元素的位置,在之后的光标移动过程中修改元素的位置使其始终保持和光标的相对位置。

代码实现:

效果演示:

<template><Drag-Elem class="drag-btn" :snapEdge="true"><button @click="onClick">💛操作元素</button><template #snapEdge><button @touch="onClick">💛</button></template></Drag-Elem>
</template>
<style>.drag-btn {bottom: 100px;left: 10px;}
</style>
<script setup>
import DragElem from '@/components/myUI/DragElem.vue';
const onClick = () => alert('💛点击')
</script> 

------ 最后附上代码:

1.template & style

<template><!-- 移动容器 --><div ref="dragContainerRef" class="drag-container" :style="dragContainerStyle"><!-- 可拖拽元素,拖拽该元素会对整个移动容器进行移动 --><div draggable="true" @dragstart="onDragstart" @dragend="onDragend" @touchstart="onTouchstart"@touchmove="onTouchmove" @touchend="onTouchend"><div :style="dragElemStyle"><div v-show="$slots.snapEdge&&isShowSnapEdgeElem" @mouseup="unShowSnapEdgeElem"><slot name="snapEdge"></slot></div><div v-show="!$slots.snapEdge"><slot></slot></div></div></div><!-- 操作元素,由可拖拽元素点击触发弹出 --><div v-show="$slots.snapEdge && !($slots.snapEdge && isShowSnapEdgeElem)"><slot></slot></div></div>
</template><style>.drag-container {position: fixed;z-index: 10;}
</style> 

2.typescript

import { onMounted, reactive, ref } from 'vue';// component props
// :snapEdge="true" 开启元素侧边栏吸附 
const props = defineProps({snapEdge: {type: Boolean,required: false,},
})
// 移动容器位置
const dragContainerStyle = reactive({top: '',left: '',bottom: '',right: '',
});
// 设置可拖拽元素拖拽时大小(仅对移动端生效)
const SCALE = '1.5'
const dragElemStyle = reactive({transform: 'scale(1)',
})
// 是否显示侧边栏吸附元素,仅在使用了$slots.snapEdge插槽时生效
const isShowSnapEdgeElem = ref(false)const dragContainerRef = ref<HTMLElement>(null)
const initLocation = () => {const { offsetLeft, offsetTop } = dragContainerRef.valuedragContainerStyle.top = offsetTop + 'px'dragContainerStyle.left = offsetLeft + 'px'dragContainerStyle.bottom = 'auto'dragContainerStyle.right = 'auto'setElemSnapEdgeLocation()
}
onMounted(initLocation)let pointerRelativeX: number, pointerRelativeY: number;
// 记录指针相对元素位置
const recordPointerLocation = (clientX: number, clientY: number) => {pointerRelativeX = clientX - dragContainerRef.value.offsetLeft;pointerRelativeY = clientY - dragContainerRef.value.offsetTop;
};
// 模式一:设置目标元素位置,以指针为基点
const setElemLocation = (clientX: number, clientY: number) => {const left = clientX - pointerRelativeX;const top = clientY - pointerRelativeY;dragContainerStyle.right = 'auto';dragContainerStyle.bottom = 'auto';dragContainerStyle.top = top + 'px';dragContainerStyle.left = left + 'px';
};
// 模式二:设置目标元素吸附位置
const setElemSnapEdgeLocation = () => {if (!props.snapEdge) return;const { offsetLeft, offsetWidth } = dragContainerRef.valueconst { innerWidth } = windowif (offsetLeft + offsetWidth / 2 < innerWidth / 2) {dragContainerStyle.left = '0px'dragContainerStyle.right = 'auto'} else {dragContainerStyle.right = '0px'dragContainerStyle.left = 'auto'}isShowSnapEdgeElem.value = true
};// 隐藏吸附边缘的元素,显示操作元素
const unShowSnapEdgeElem = () => {isShowSnapEdgeElem.value = falsesetTimeout(() => { document.addEventListener('click', showSnapEdgeElem) })
}
const showSnapEdgeElem = () => {document.removeEventListener('click', showSnapEdgeElem)isShowSnapEdgeElem.value = true
}// pc端鼠标拖拽事件
const onDragstart = (evt: DragEvent) => {evt.preventDefault();evt.stopPropagation();const { clientX, clientY } = evt;dragContainerRef.value.addEventListener('dragover', prevent);dragContainerRef.value.parentNode.addEventListener('dragover', prevent);recordPointerLocation(clientX, clientY);
};
const onDragend = (evt: DragEvent) => {evt.preventDefault();evt.stopPropagation();const { clientX, clientY, target } = evt;dragContainerRef.value.removeEventListener('dragover', prevent);dragContainerRef.value.parentNode.addEventListener('dragover', prevent);setElemLocation(clientX, clientY);setElemSnapEdgeLocation()
};
const prevent = (evt: DragEvent) => {evt.preventDefault();evt.dataTransfer.dropEffect = 'move'
};// 移动端
const onTouchstart = (evt: TouchEvent) => {evt.stopPropagation();const { clientX, clientY } = evt.touches[0];recordPointerLocation(clientX, clientY);dragElemStyle.transform = `scale(${SCALE})`
};
const onTouchmove = (evt: TouchEvent) => {evt.preventDefault();evt.stopPropagation();const { clientX, clientY } = evt.touches[0];setElemLocation(clientX, clientY);
};
const onTouchend = (evt: TouchEvent) => {evt.stopPropagation();setElemSnapEdgeLocation()dragElemStyle.transform = `scale(1)`
}; 

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取


http://chatgpt.dhexx.cn/article/LhHytUAR.shtml

相关文章

Vue:可拖拽组件

在实际开发中&#xff0c;很可能会遇到开发可拖拽组件的需求&#xff0c;目的是应对某些弹框组件会遮盖某些重要信息/可操作面板&#xff0c;通过可拖拽的形式可以将上层的弹框组件移动到其他位置&#xff0c;从而不影响整个系统的操作。下面&#xff0c;我们分两步走&#xff…

vue+ element 三元运算符设置按钮样式

效果图 代码块 <el-button-group><el-button size"small" click"notSelect()" :class"[status 1?isActive:]" autofocus"true" v-focus plain>待选</el-button><el-button size"small" click"…

vue三目运算符可以写html代码么,Vue-js之文本渲染、属性(样式)绑定、三元运算符、事件处理...

文本渲染之3种方式 1.{{}}&#xff1a;插值表达式 弊端&#xff1a;会出现页面闪烁问题 v-text&#xff1a;把vue实例中的内容当作纯文本&#xff0c;显示在浏览器 v-html&#xff1a;把vue实例中的内容进行渲染后&#xff0c;浏览器会再次执行文本渲染 {{msg}} let vm new Vu…

vue中的三元运算符

我们要做的效果如下&#xff1a; 如上图的全部分类效果&#xff0c;符号、的样式具体写法如下图&#xff1a;数据如下&#xff1a; 补充&#xff1a;我们一开始想的可能是在数据中直接加符号、&#xff0c;但是脑子想让我换个想法&#xff0c;所以想先控制显示&#xff0c;第一…

vue 在 html标签上 做三元运算

众所周知&#xff0c;在js 中 a<b?‘方法一’:‘方法二’ 这种 三元运算 是比较方便快捷&#xff0c;那么如何在html标签上直接使用呢&#xff1f; 在 标签包裹中&#xff0c;一半为内容&#xff0c;可以直接使用 例如&#xff1a; <p class"price">{{p…

抖音快手短视频去水印API,接口开发文档

开发者官网&#xff1a;http://api.lingquan166.com/ 简介&#xff1a;根据抖音、微视、小红书、皮皮搞笑等APP中复制出来的链接&#xff0c;解析获取短视频的标题、封面、无水印短视频地址等信息。 接口地址&#xff1a; https://api.lingquan166.com/dsp?token122917VU601…

【嵌入式开发】ARM 关闭中断 ( CPRS 中断控制位 | 中断使能寄存器 | 中断屏蔽寄存器 | 关闭中断 | 汇编代码编写 )

一. 中断控制 ( 基于 S3C6410 开发板 ) 1. 关闭中断的两个步骤 (1) 关闭中断步骤 2. CPRS 寄存器中的中断控制位 (1) CPRS 寄存器简介(2) CPRS 寄存器 中断控制 相关 位 3. 中断使能寄存器 (1) 中断使能寄存器简介(2) 中断屏蔽寄存器简介 二. 关闭中断 代码示例 1. 汇编代码编…

图像哈希算法

最近在回顾一些经典的图像哈希算法&#xff0c;本文大致介绍了一些常用的图像哈希算法&#xff0c;暂时先列一个框架&#xff0c;待日后补充。 参考链接&#xff1a; 【1】基于内容的图像哈希算法研究 【2】图像聚类-谱聚类 【3】球哈希Spherical Hashing 部分哈希源码及大牛…

消息机制(Handle机制)

消息机制简介- Handler是Android消息机制的上层接口。Handle的使用过程简单&#xff0c;通过它可以轻松将一个任务切换到Handle所在的线程中去执行。通常情况下&#xff0c;Handle的使用场景是更新UI。 在子线程中进行耗时操作&#xff0c;执行完操作后&#xff0c;发送消息&…

魔百盒九联UNT402A_S905L3_线刷固件包_语音蓝牙正常

魔百盒九联UNT402A_S905L3_线刷固件包_语音蓝牙正常 固件特点&#xff1a; 1、适用于九联UNT402A&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&#xff0c;运行速度提升…

大规模特征向量检索算法总结 (LSH PQ HNSW)

大规模特征向量检索算法总结 (LSH PQ HNSW) 转自&#xff1a;https://www.6aiq.com/article/1587522027341 向量检索基本概念 向量从表现形式上就是一个一维数组。我们需要解决的问题是使用下面的公式度量距离寻找最相似的 K 个向量。 欧式距离&#xff1a; 两点间的真实距…

面向高光谱图像的多特征融合哈希

原文&#xff1a;Efficient Multiple Feature Fusion With Hashing for Hyperspectral Imagery Classification: A Comparative Study 面向高光谱图像的多特征融合哈希 I. IntroductionII. MFH FrameworkIII. Feature HashingIV. Experiment SettingA. Data SetsB. Multiple Fe…

论文笔记-迭代量化哈希ITQ

推荐一个大牛的博客&#xff1a;http://yongyuan.name/【这篇博文也借鉴了他的博客&#xff0c;建议直接看他的博文加上论文】 自己的这个就是一个论文笔记&#xff0c;为了自己日后复习&#xff1b; 【待补充...因为笔记太乱了 懒得整理 】

ITQ(Iterative Quantization)迭代量化方法详解 hash 哈希算法

看过的文章&#xff0c;不做记录&#xff0c;即便当时理解透了&#xff0c;过一段时间后&#xff0c;知识总会模糊不清。所以从现在开始&#xff0c;对一些自己阅读过的一些精彩的文章&#xff0c;悉心记录&#xff0c;方便自己查阅温故&#xff0c;当然如果对同行有所裨益的话…

time svd java代码_ITQ算法理解 - 漫长当下TIME的个人空间 - OSCHINA - 中文开源技术交流社区...

IterativeQuantization: A Procrustean Approach to Learning Binary Codes 论文理解及代码讲解 这篇文章发表在2011年CVRP上&#xff0c;一作是Yunchao Gong&#xff0c;师从Sanjiv Kumar&#xff0c;关于Sanjiv Kumar可以到她的HomePage上了解。 文章目的&#xff1a;学习保留…

Iterative Quantization,ITQ

Abstract 针对大规模的图像检索问题&#xff0c;论文提出了一个高效的ITQ算法。该算法先将中心化后的数据映射到超立方体的顶点上&#xff0c;再通过优化过程寻找一个旋转矩阵&#xff0c;使得数据点经过旋转后&#xff0c;与超立方体的顶点数据具有最小的量化误差。ITQ算法涉及…

ITQ算法理解

开发十年&#xff0c;就只剩下这套Java开发体系了 >>> IterativeQuantization: A Procrustean Approach to Learning Binary Codes 论文理解及代码讲解 这篇文章发表在2011年CVRP上&#xff0c;一作是Yunchao Gong&#xff0c;师从Sanjiv Kumar&#xff0c;关于San…

ITQ(Iterative Quantization)图像检索算法

开发十年&#xff0c;就只剩下这套Java开发体系了 >>> 1、文章简介 名称&#xff1a;Iterative Quantization:A Procrustean Approach to Learning Binary Codes 这篇文章发表与2011年CVRP(Computer Vision & Pattern Recognition)&#xff0c;作者:Yunchao Go…

ITQ(Iterative Quantization)迭代量化方法详解

转载&#xff1a;http://www.yuanyong.org/cv/itq-hashing.html CVPR 2011《Iterative Quantization: A Procrustean Approach to Learning Binary Codes》论文阅读笔记。 看过的文章&#xff0c;不做记录&#xff0c;即便当时理解透了&#xff0c;过一段时间后&#xff0c;知识…

怎么查看mysql是否锁表_MySQL查看是否锁表

MySQL查看是否锁表的方法:首先进入命令窗口;然后通过执行命令“show engine innodb status\G;”查看造成死锁的sql语句,并分析索引情况即可。 可直接在mysql命令行执行:show engine innodb status\G; 查看造成死锁的sql语句,分析索引情况,然后优化sql然后show processlis…