vue3 让元素可以拖动指令v-draggable
第一种情况:仅需移动
仅需要使得某个容器可以拖动,仅此而已
定义指令
// v-draggable.ts
type DraggableElement = HTMLElement & {
_cleanupDrag?: () => void;
};
function setupDraggable(el: DraggableElement) {
// 清除旧的拖拽监听器(避免重复绑定)
el._cleanupDrag?.();
let startX = 0;
let startY = 0;
let currentX = 0;
let currentY = 0;
// 初始化位置信息
el.dataset.currentX = String(currentX);
el.dataset.currentY = String(currentY);
// 设置样式
el.style.position = "fixed";
el.style.cursor = "move";
el.style.transform = `translate(${currentX}px, ${currentY}px)`;
// 鼠标按下时,准备拖动
const onMouseDown = (event: MouseEvent) => {
event.preventDefault();
startX = event.clientX - Number(el.dataset.currentX);
startY = event.clientY - Number(el.dataset.currentY);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};
// 拖动过程中
const onMouseMove = (event: MouseEvent) => {
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
const { width, height } = el.getBoundingClientRect();
const maxX = window.innerWidth - width;
const maxY = window.innerHeight - height;
const newX = Math.max(0, Math.min(deltaX, maxX));
const newY = Math.max(0, Math.min(deltaY, maxY));
el.dataset.currentX = String(newX);
el.dataset.currentY = String(newY);
el.style.transform = `translate(${newX}px, ${newY}px)`;
};
// 拖动结束,移除监听器
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
// 绑定初始事件
el.addEventListener("mousedown", onMouseDown);
// 提供清理函数
el._cleanupDrag = () => {
el.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
}
// Vue 指令定义
const vDraggable = {
mounted(el: DraggableElement) {
setupDraggable(el);
},
unmounted(el: DraggableElement) {
el._cleanupDrag?.();
},
};
export default vDraggable;
使用
app.vue中使用
<template>
<div class="app">
<div class="card" v-draggable></div>
</div>
</template>
<script setup lang="ts">
import vDraggable from "../directives/v-draggable";
</script>
<style scoped>
.card {
width: 100px;
height: 100px;
background-color: red;
}
</style>
效果

第二种情况:放大缩小
加上了工具栏,上边有放大缩小的按钮,并且根据点击放大缩小的按钮,指令要监听传入的窗口状态更新大小和位置
定义指令
// v-draggable.ts
type DraggableElement = HTMLElement & {
_cleanupDrag?: () => void;
};
const minSize = {
width: 260,
height: 150,
};
const maxSize = {
width: 400,
height: 200,
};
function setupDraggable(el: DraggableElement, params) {
// 清除旧的拖拽监听器(避免重复绑定)
el._cleanupDrag?.();
let startX = 0;
let startY = 0;
let currentX = 20;
let currentY = 20;
// 计算位置
if (params.big) {
// 如果是大就--->居中对齐
const rect = el.getBoundingClientRect();
currentX = (window.innerWidth - rect.width) / 2;
currentY = (window.innerHeight - rect.height) / 2;
}
// 初始化位置信息
el.dataset.currentX = String(currentX);
el.dataset.currentY = String(currentY);
// 设置样式
el.style.width = params.big ? `${maxSize.width}px` : `${minSize.width}px`;
el.style.height = params.big ? `${maxSize.height}px` : `${minSize.height}px`;
el.style.position = "fixed";
el.style.cursor = "move";
el.style.transform = `translate(${currentX}px, ${currentY}px)`;
// 鼠标按下时,准备拖动
const onMouseDown = (event: MouseEvent) => {
event.preventDefault();
startX = event.clientX - Number(el.dataset.currentX);
startY = event.clientY - Number(el.dataset.currentY);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};
// 拖动过程中
const onMouseMove = (event: MouseEvent) => {
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
const { width, height } = el.getBoundingClientRect();
const maxX = window.innerWidth - width;
const maxY = window.innerHeight - height;
const newX = Math.max(0, Math.min(deltaX, maxX));
const newY = Math.max(0, Math.min(deltaY, maxY));
el.dataset.currentX = String(newX);
el.dataset.currentY = String(newY);
el.style.transform = `translate(${newX}px, ${newY}px)`;
};
// 拖动结束,移除监听器
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
// 绑定初始事件
el.addEventListener("mousedown", onMouseDown);
// 提供清理函数
el._cleanupDrag = () => {
el.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
}
// Vue 指令定义
const vDraggable = {
mounted(el: DraggableElement, binding: any) {
setupDraggable(el, binding.value);
},
updated(el: DraggableElement, binding: any) {
setupDraggable(el, binding.value);
},
unmounted(el: DraggableElement) {
el._cleanupDrag?.();
},
};
export default vDraggable;
使用
app.vue
<template>
<div class="app">
<div class="card" v-draggable="{ big: winBig }">
<div class="tool">
<span @click="setWinBig" v-if="winBig">-</span>
<span @click="setWinBig" v-else>+</span>
</div>
<div class="content"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import vDraggable from "../directives/v-draggable";
const winBig = ref(false);
const setWinBig = () => {
winBig.value = !winBig.value;
console.log(winBig.value);
};
</script>
<style scoped lang="scss">
.card {
display: flex;
flex-direction: column;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
.tool {
gap: 20px;
box-sizing: border-box;
height: 40px;
width: 100%;
background-color: white;
display: flex;
align-items: center;
justify-content: end;
padding: 10px;
border-bottom: rgba(0, 0, 0, 0.1) solid 1px;
>span{
cursor: pointer;
}
}
.content {
overflow: auto;
flex: 1;
}
}
</style>
效果

第三种情况: iframe
如果在上一种情况的 content 里放入 iframe,你再拖动就会出现粘手现象,这是因为:你的鼠标在拖动中进入了 iframe,浏览器会将事件“转交”给 iframe 的上下文(也就是 iframe 里面的页面),于是外部页面就无法再监听 mousemove 和 mouseup 事件,拖动就中断了。
解决方案:用“透明遮罩层”挡住 iframe
// v-draggable.ts
type DraggableElement = HTMLElement & {
_cleanupDrag?: () => void;
};
// 如果不是 iframe 则不用遮罩
function addMask() {
if (document.querySelector(".drag-mask")) return;
const mask = document.createElement("div");
mask.style.position = "fixed";
mask.style.top = "0";
mask.style.left = "0";
mask.style.width = "100vw";
mask.style.height = "100vh";
mask.style.zIndex = "9999"; // 保证在所有元素之上
mask.style.cursor = "move";
mask.style.background = "transparent"; // 保证你还能看见 iframe
mask.className = "drag-mask";
document.body.appendChild(mask);
}
function removeMask() {
const mask = document.querySelector(".drag-mask");
if (mask) document.body.removeChild(mask);
}
const minSize = {
width: 260,
height: 150,
};
const maxSize = {
width: 400,
height: 200,
};
function setupDraggable(el: DraggableElement, params) {
// 清除旧的拖拽监听器(避免重复绑定)
el._cleanupDrag?.();
let startX = 0;
let startY = 0;
let currentX = 20;
let currentY = 20;
// 计算位置
if (params.big) {
// 如果是大就--->居中对齐
const rect = el.getBoundingClientRect();
currentX = (window.innerWidth - rect.width) / 2;
currentY = (window.innerHeight - rect.height) / 2;
}
// 初始化位置信息
el.dataset.currentX = String(currentX);
el.dataset.currentY = String(currentY);
// 设置样式
el.style.width = params.big ? `${maxSize.width}px` : `${minSize.width}px`;
el.style.height = params.big ? `${maxSize.height}px` : `${minSize.height}px`;
el.style.position = "fixed";
el.style.cursor = "move";
el.style.transform = `translate(${currentX}px, ${currentY}px)`;
// 鼠标按下时,准备拖动
const onMouseDown = (event: MouseEvent) => {
event.preventDefault();
startX = event.clientX - Number(el.dataset.currentX);
startY = event.clientY - Number(el.dataset.currentY);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};
// 拖动过程中
const onMouseMove = (event: MouseEvent) => {
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
// 如果移动距离超过 3px,添加遮罩
const movedDistance = Math.sqrt(
(deltaX - Number(el.dataset.currentX)) ** 2 +
(deltaY - Number(el.dataset.currentY)) ** 2
);
if (movedDistance > 3) {
addMask();
}
const { width, height } = el.getBoundingClientRect();
const maxX = window.innerWidth - width;
const maxY = window.innerHeight - height;
const newX = Math.max(0, Math.min(deltaX, maxX));
const newY = Math.max(0, Math.min(deltaY, maxY));
el.dataset.currentX = String(newX);
el.dataset.currentY = String(newY);
el.style.transform = `translate(${newX}px, ${newY}px)`;
};
// 拖动结束,移除监听器
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
removeMask();
};
// 绑定初始事件
el.addEventListener("mousedown", onMouseDown);
// 提供清理函数
el._cleanupDrag = () => {
el.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
removeMask();
};
}
// Vue 指令定义
const vDraggable = {
mounted(el: DraggableElement, binding: any) {
setupDraggable(el, binding.value);
},
updated(el: DraggableElement, binding: any) {
setupDraggable(el, binding.value);
},
unmounted(el: DraggableElement) {
el._cleanupDrag?.();
},
};
export default vDraggable;
// app.vue
<template>
<div class="app">
<div class="card" v-draggable="{ big: winBig }">
<div class="tool">
<span @click="setWinBig" v-if="winBig">-</span>
<span @click="setWinBig" v-else>+</span>
</div>
<div class="content">
<iframe
src="https://www.cnblogs.com"
width="100%"
height="100%"
frameborder="0"
></iframe>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import vDraggable from "../directives/v-draggable";
const winBig = ref(false);
const setWinBig = () => {
winBig.value = !winBig.value;
console.log(winBig.value);
};
</script>
<style scoped lang="scss">
.card {
display: flex;
flex-direction: column;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
.tool {
gap: 20px;
box-sizing: border-box;
height: 40px;
width: 100%;
background-color: white;
display: flex;
align-items: center;
justify-content: end;
padding: 10px;
border-bottom: rgba(0, 0, 0, 0.1) solid 1px;
> span {
cursor: pointer;
}
}
.content {
overflow: auto;
flex: 1;
}
}
</style>
vue3 让元素可以拖动指令v-draggable的更多相关文章
- HTML5 元素拖动 - 实现元素左右拖动, 或更改自身排序
1.拖放(Drag 和 drop)是 HTML5 标准的组成部分. 拖放是一种常见的特性,即抓取对象以后拖到另一个位置.在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放. 浏览器支持:Int ...
- 如何在VC++ 6.0中实现拖动指令改变执行路径?
前文提要: 在VC6.0之后出现的VS系列开发工具都具有的调试功能:移动指针更改执行流,VC6不支持这个UI操作. 调试程序暂停时,源代码或"反汇编"窗口边距处的黄色箭头标记要运行 ...
- vue自定义拖动指令
1.在项目开发中,需要对div进行拖动.因为需要自定义组件 a>定义全局拖拽指令: 定义全局指令,需要在main.js中写入vue.directive('drag',{});即可.但是一般会在外 ...
- 设置dom元素可拖动,支持ie5+
摘要: 最近在项目中要做一个图片预览的功能,这时候会遇到用户上传很大的图片,已经超出视图界面.最终决定做一个在固定宽和高的位置,用户可以拖动图片查看.所以自己就写了一个支持ie5+,chrome,Fi ...
- Canvas中鼠标获取元素并拖动技术
Silverlight拖动,需要Canvas. Canvas管网定义: 定义一个区域,在该区域中可以使用相对于该区域的坐标显式定位子元素. XAML <Canvas ...> oneOrM ...
- jsp动作元素之forward指令
forward指令用于将页面响应转发到另外的页面.既可以转发到静态的HTML页面,也可以转发到动态的JSP页面,或者转发到容器中的Servlet. forward指令格式如下: <jsp:for ...
- JAVA-JSP指令元素之page指令
相关资料:<21天学通Java Web开发> 结果总结:1.page设定JSP页面全局属性,作用于整个JSP页面,包括静态包含的文件2.<%@ page 属性1="属性值1 ...
- 元素JS拖动的实现
涉及到了几个位置的属性 offset clientX cilentY 等 $(selector).on("mousedown",function (e){ var x = e. ...
- react项目中实现元素的拖动和缩放实例
在react项目中实现此功能可借助 react-rnd 库,文档地址:https://github.com/bokuweb/react-rnd#Screenshot .下面是实例运用: import ...
- HTML5(Canvas Vedio Audio 拖动)
1.Canvas (在画布上(Canvas)画一个红色矩形,渐变矩形,彩色矩形,和一些彩色的文字) HTML5 元素用于图形的绘制,通过脚本 (通常是JavaScript)来完成. 标签只是图形 ...
随机推荐
- Linux | 如何创建一个 home 目录在 /data 磁盘的 sudo 用户
需求: 拿到了 boss 的服务器账号 ssh boss@172.16.1.100,需要登录 boss 的账号,然后为自己创建一个账号,实现 ssh <user_name>@172.16. ...
- nodejs目录与文件遍历
路径相关函数 path.basename('/foo/bar/baz/asdf/quux.html'); // Returns: 'quux.html' path.basename('/foo/bar ...
- RocketMQ消息是如何存储的
RocketMQ的消息存储是一个复杂而高效的过程,设计上充分考虑了性能和扩展性, 消息存储的主要组件包括CommitLog文件.消费队列文件(ConsumerQueue).以及索引文件(IndexFi ...
- java基础之Stream流
一.使用Stream的目的:用于解决已有集合类库既有的弊端,只求关注[目的],不关注[方式],且其数据源:可以是集合,数组等 例子: public class NormalFilter { publi ...
- kettle介绍-Step之CSV Input
CSV Input/CSV 文件输入介绍 CSV 文件输入步骤主要用于将 CSV 格式的文本文件按照一定的格式输入至 流中 Step name:步骤的名称,在单一转换中,名称必须唯一 Filename ...
- 🚀 放弃 Oh-My-Posh,转而手搓 FastPrompt,打造快速高效的命令提示
「够用.够快.够自由」才是我心目中的终端提示符. 一个开发者的烦恼 每天打开 PowerShell,等待提示符加载完毕,我的内心就像在等待一个磨蹭的同事. 我用的是 Windows Terminal ...
- 重磅升级!MCPmarket.cn 开启云托管时代,打造智能体开发的“App Store“ , 一键即可直连MCP工具百宝箱
[关键词:MCP, MCPmarket, Claude, Cursor, AI Agent, 云托管 MCP, Model Context Protocol, MCP Server 部署, Claud ...
- 解决chrome浏览器拓展插件颜色变成透明无法使用。
虚拟机装了chrome之后插件变成了透明的,没办法使用了. 解决办法如下: 1.卸载VMWARE tools 2.地址栏输入: chrome://flags 找到 "Choose ANGLE ...
- 【HUST】网安|操作系统实验|实验三 内存管理
文章目录 任务 任务1 Win/Linux编写二维数组遍历程序,理解局部性的原理. 1. 提示 2. 任务代码 3. 结果及说明 任务2 Windows/Linux模拟实现OPT和LRU淘汰算法. 1 ...
- Jmeter+Ant+Jenkins接口自动化测试(三)_Ant配置及Jenkins持续集成
前言: 本来想多分几部分,但是都是抽时间总结的,也就不润色了,直接三板斧,结束. 特别提示: 知识是用来分享的,但是也要尊重作者的权益,转载请注明出处,未经本人允许不可用于商业目的. Ant构建文件配 ...