第一种情况:仅需移动

仅需要使得某个容器可以拖动,仅此而已

定义指令

// 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的更多相关文章

  1. HTML5 元素拖动 - 实现元素左右拖动, 或更改自身排序

    1.拖放(Drag 和 drop)是 HTML5 标准的组成部分. 拖放是一种常见的特性,即抓取对象以后拖到另一个位置.在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放. 浏览器支持:Int ...

  2. 如何在VC++ 6.0中实现拖动指令改变执行路径?

    前文提要: 在VC6.0之后出现的VS系列开发工具都具有的调试功能:移动指针更改执行流,VC6不支持这个UI操作. 调试程序暂停时,源代码或"反汇编"窗口边距处的黄色箭头标记要运行 ...

  3. vue自定义拖动指令

    1.在项目开发中,需要对div进行拖动.因为需要自定义组件 a>定义全局拖拽指令: 定义全局指令,需要在main.js中写入vue.directive('drag',{});即可.但是一般会在外 ...

  4. 设置dom元素可拖动,支持ie5+

    摘要: 最近在项目中要做一个图片预览的功能,这时候会遇到用户上传很大的图片,已经超出视图界面.最终决定做一个在固定宽和高的位置,用户可以拖动图片查看.所以自己就写了一个支持ie5+,chrome,Fi ...

  5. Canvas中鼠标获取元素并拖动技术

    Silverlight拖动,需要Canvas. Canvas管网定义: 定义一个区域,在该区域中可以使用相对于该区域的坐标显式定位子元素. XAML <Canvas ...> oneOrM ...

  6. jsp动作元素之forward指令

    forward指令用于将页面响应转发到另外的页面.既可以转发到静态的HTML页面,也可以转发到动态的JSP页面,或者转发到容器中的Servlet. forward指令格式如下: <jsp:for ...

  7. JAVA-JSP指令元素之page指令

    相关资料:<21天学通Java Web开发> 结果总结:1.page设定JSP页面全局属性,作用于整个JSP页面,包括静态包含的文件2.<%@ page 属性1="属性值1 ...

  8. 元素JS拖动的实现

    涉及到了几个位置的属性 offset   clientX cilentY 等 $(selector).on("mousedown",function (e){ var x = e. ...

  9. react项目中实现元素的拖动和缩放实例

    在react项目中实现此功能可借助 react-rnd 库,文档地址:https://github.com/bokuweb/react-rnd#Screenshot .下面是实例运用: import ...

  10. HTML5(Canvas Vedio Audio 拖动)

    1.Canvas    (在画布上(Canvas)画一个红色矩形,渐变矩形,彩色矩形,和一些彩色的文字) HTML5 元素用于图形的绘制,通过脚本 (通常是JavaScript)来完成. 标签只是图形 ...

随机推荐

  1. Linux | 如何创建一个 home 目录在 /data 磁盘的 sudo 用户

    需求: 拿到了 boss 的服务器账号 ssh boss@172.16.1.100,需要登录 boss 的账号,然后为自己创建一个账号,实现 ssh <user_name>@172.16. ...

  2. nodejs目录与文件遍历

    路径相关函数 path.basename('/foo/bar/baz/asdf/quux.html'); // Returns: 'quux.html' path.basename('/foo/bar ...

  3. RocketMQ消息是如何存储的

    RocketMQ的消息存储是一个复杂而高效的过程,设计上充分考虑了性能和扩展性, 消息存储的主要组件包括CommitLog文件.消费队列文件(ConsumerQueue).以及索引文件(IndexFi ...

  4. java基础之Stream流

    一.使用Stream的目的:用于解决已有集合类库既有的弊端,只求关注[目的],不关注[方式],且其数据源:可以是集合,数组等 例子: public class NormalFilter { publi ...

  5. kettle介绍-Step之CSV Input

    CSV Input/CSV 文件输入介绍 CSV 文件输入步骤主要用于将 CSV 格式的文本文件按照一定的格式输入至 流中 Step name:步骤的名称,在单一转换中,名称必须唯一 Filename ...

  6. 🚀 放弃 Oh-My-Posh,转而手搓 FastPrompt,打造快速高效的命令提示

    「够用.够快.够自由」才是我心目中的终端提示符. 一个开发者的烦恼 每天打开 PowerShell,等待提示符加载完毕,我的内心就像在等待一个磨蹭的同事. 我用的是 Windows Terminal ...

  7. 重磅升级!MCPmarket.cn 开启云托管时代,打造智能体开发的“App Store“ , 一键即可直连MCP工具百宝箱

    [关键词:MCP, MCPmarket, Claude, Cursor, AI Agent, 云托管 MCP, Model Context Protocol, MCP Server 部署, Claud ...

  8. 解决chrome浏览器拓展插件颜色变成透明无法使用。

    虚拟机装了chrome之后插件变成了透明的,没办法使用了. 解决办法如下: 1.卸载VMWARE tools 2.地址栏输入: chrome://flags 找到 "Choose ANGLE ...

  9. 【HUST】网安|操作系统实验|实验三 内存管理

    文章目录 任务 任务1 Win/Linux编写二维数组遍历程序,理解局部性的原理. 1. 提示 2. 任务代码 3. 结果及说明 任务2 Windows/Linux模拟实现OPT和LRU淘汰算法. 1 ...

  10. Jmeter+Ant+Jenkins接口自动化测试(三)_Ant配置及Jenkins持续集成

    前言: 本来想多分几部分,但是都是抽时间总结的,也就不润色了,直接三板斧,结束. 特别提示: 知识是用来分享的,但是也要尊重作者的权益,转载请注明出处,未经本人允许不可用于商业目的. Ant构建文件配 ...