第一种情况:仅需移动

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

定义指令

// 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. 2025年最流行的5个Python ASGI服务器及其核心特性与适用场景

    以下是2025年最流行的5个Python ASGI服务器及其核心特性与适用场景: 1. Uvicorn • 核心优势: • 基于uvloop和httptools,性能远超传统WSGI服务器,支持HTT ...

  2. Linux 关机与重启命令

    关机命令 我们可以使用以下三种命令来关机 Linux : 1.立刻关机(需要root用户) shutdown -h now 10 分钟后自动关机 shutdown -h 10 2.立刻关机 halt ...

  3. 推荐IT公司历史精品书籍

    浪潮之巅,吴军注 讲述了各大it公司发展历程,从兴起,到转折或衰落,蓝图宏伟,对现在的各大公司和技术发展的理解有一些帮助. 包含AT & T,IBM,微软,苹果,亚马逊等公司.

  4. 🎀java-自定义日志注解

    简介 创建自定义日志注解,对相关接口记录请求日志. 环境 SpringBoot 实现 注解定义 定义注解类 package com.zk.app.annotation; import com.zk.a ...

  5. python-docx 设置表格边框

    # -*- coding: utf-8 -*- """ Created on Sat Oct 24 17:21:31 2020 pip install -i https: ...

  6. ChatGPT为何放弃WebSocket?揭秘EventSource的三大决胜优势

    ChatGPT为何放弃WebSocket?揭秘EventSource的三大决胜优势 感觉本篇对你有帮助可以关注一下我的微信公众号(深入浅出谈java),会不定期更新知识和面试资料.技巧!!! Chat ...

  7. Form验证笔记

    views    request.body        request.POST(request.body)        request.FILES(request.body)        re ...

  8. String to Integer (atoi)——LeetCode进阶路⑧

    原题链接https://leetcode.com/problems/string-to-integer-atoi/ 说实话,看到这道题之前,看这通过率有点慌,到底是因为啥 让一道medium的题目这么 ...

  9. K8s进阶之LimitRange

    概述 官方文档:https://kubernetes.io/zh-cn/docs/concepts/policy/limit-range/ 在 Kubernetes(K8s)中,LimitRange ...

  10. 聊一聊 C# NativeAOT 多平台下的函数导出

    一:背景 1. 讲故事 昨晚训练营里有一位朋友提到一个问题,说 C# AOT程序能否编译为一个dll,供其他语言调用,其实这个是完全没有问题的,也确实我的的文章体系中没有涉及到这块,那今天就补充完整吧 ...