基于 Vue 的拖拽缩放卡片组件:实现思路、方法及使用指南
引言
在前端开发中,实现可交互的组件能够极大地提升用户体验。本文将介绍一个基于 Vue 封装的可缩放卡片组件,从实现思路、代码具体实现以及使用方法等方面进行详细阐述,帮助开发者更好地理解和运用这一组件。项目源码地址:https://gitcode.com/Jiaberrr/vue3-pc-template
实现思路
- 定位与布局:通过
position: absolute
对卡片进行定位,利用left
、top
、right
、bottom
属性确定其在页面中的位置,同时设置width
和height
来定义卡片的初始大小。 - 缩放控制点:在卡片的四个角(左上角、右上角、左下角、右下角)添加可交互的缩放控制点,通过监听这些控制点的鼠标事件(
mousedown
、mousemove
、mouseup
)来实现卡片的缩放功能。 - 状态跟踪:使用变量来记录卡片的初始大小、位置以及鼠标的初始位置,在缩放过程中根据鼠标的移动距离计算卡片新的大小和位置。
代码实现
模板部分(template)
<template><div class="absolute" :id="idName" :style="{width: width,height: height,top: top + 'px',left: left + 'px',right: right + 'px',bottom: bottom + 'px'}"><slot></slot><div class="resize-handle-tl" :class="'resize-handle'+ idName"></div><div class="resize-handle-tr" :class="'resize-handle'+ idName"></div><div class="resize-handle-bl" :class="'resize-handle'+ idName"></div><div class="resize-handle-br" :class="'resize-handle'+ idName"></div></div>
</template>
在模板中,外层div
通过id
和style
绑定来设置卡片的位置和大小。slot
用于插入卡片的内容,四个角的div
分别代表缩放控制点,通过动态绑定类名来标识不同的控制点。
script 部分(script setup)
import { onMounted } from "vue";const porp = defineProps({idName: {Type: String,required: true},width: {type: [Number, String],default: "100%", // 默认宽度},height: {type: [Number, String],default: "100%", // 默认高度},top: {type: Number,default: null,},left: {type: Number,default: null,},bottom: {type: Number,default: null,},right: {type: Number,default: null,}
})let originalWidth = 0;
let originalHeight = 0;
let originalX = 0;
let originalY = 0;
let originalMouseX = 0;
let originalMouseY = 0;
let resizableBox = null;
let resizeHandle = [];
let resizeType = "";onMounted(() => {resizableBox = document.getElementById(porp.idName);resizeHandle = document.querySelectorAll(".resize-handle"+ porp.idName);resizeHandle.forEach((handle) => {handle.addEventListener("mousedown", function (e) {e.preventDefault();originalWidth = parseFloat(getComputedStyle(resizableBox).width);originalHeight = parseFloat(getComputedStyle(resizableBox).height);originalMouseX = e.clientX;originalMouseY = e.clientY;resizeType = this.className;window.addEventListener("mousemove", resize);window.addEventListener("mouseup", stopResize);});});
});
let firstLeft = porp.left;
let firstTop = porp.top;
let firstBottom = porp.bottom;
let firstRight = porp.right
let lastTop = 0;
let lastLeft = 0;
let lastBottom = 0;
let lastRight = 0;
const resize = (e) => {const deltaX = e.clientX - originalMouseX;const deltaY = e.clientY - originalMouseY;resizableBox = document.getElementById(porp.idName);if (resizeType.includes("resize-handle-tl")) {if (resizableBox.style.left) {resizableBox.style.left = `${originalX + deltaX + lastLeft + firstLeft}px`;resizableBox.style.top = `${originalY + deltaY + lastTop + firstTop}px`;}resizableBox.style.width = `${originalWidth - deltaX}px`;resizableBox.style.height = `${originalHeight - deltaY}px`;} else if (resizeType.includes("resize-handle-tr")) {if(resizableBox.style.top) {resizableBox.style.top = `${originalY + deltaY + firstTop + lastTop}px`;}else {resizableBox.style.right = `${ originalX - deltaX + firstRight -lastRight}px`;}resizableBox.style.width = `${originalWidth + deltaX}px`;resizableBox.style.height = `${originalHeight - deltaY}px`;} else if (resizeType.includes("resize-handle-bl")) {if( resizableBox.style.left) {resizableBox.style.left = `${originalX + deltaX + firstLeft + lastLeft}px`;}else {resizableBox.style.bottom = `${originalY - deltaY + firstBottom - lastBottom}px`;}resizableBox.style.width = `${originalWidth - deltaX}px`;resizableBox.style.height = `${originalHeight + deltaY}px`;} else if (resizeType.includes("resize-handle-br")) {if(resizableBox.style.right) {resizableBox.style.right = `${ originalX - deltaX + firstRight -lastRight}px`;resizableBox.style.bottom = `${originalY - deltaY + firstBottom - lastBottom}px`;}resizableBox.style.width = `${originalWidth + deltaX}px`;resizableBox.style.height = `${originalHeight + deltaY}px`;}
};const stopResize = (e) => {if(e.target.classList.contains('resize-handle-tl')) {lastTop += e.pageY - originalMouseY;lastLeft += e.pageX - originalMouseX;}else if(e.target.classList.contains('resize-handle-tr')) {lastTop += e.pageY - originalMouseY;lastRight += e.pageX - originalMouseX;}else if(e.target.classList.contains('resize-handle-bl')) {lastLeft += e.pageX - originalMouseX;lastBottom += e.pageY - originalMouseY}else if(e.target.classList.contains('resize-handle-br')) {lastBottom += e.pageY - originalMouseYlastRight += e.pageX - originalMouseX;}window.removeEventListener("mousemove", resize);window.removeEventListener("mouseup", stopResize);
};
- 属性定义:通过
defineProps
定义组件接受的属性,包括idName
(必选,用于唯一标识卡片)、width
、height
、top
、left
、bottom
、right
,并设置了默认值。 - 变量初始化:声明了一系列变量用于跟踪卡片的初始状态和缩放过程中的状态。
- 生命周期钩子:在
onMounted
钩子函数中,获取卡片元素和缩放控制点元素,并为每个缩放控制点添加mousedown
事件监听器。当鼠标按下时,记录卡片的初始大小和鼠标位置,同时添加mousemove
和mouseup
事件监听器。 - 缩放函数:
resize
函数根据鼠标移动的距离和缩放控制点的类型来计算并更新卡片的大小和位置。 - 停止缩放函数:
stopResize
函数在鼠标松开时,移除mousemove
和mouseup
事件监听器,并更新卡片位置的累计偏移量。
样式部分(style scoped)
.resize-handle-br {width: 10px;height: 10px;position: absolute;bottom: 0;right: 0;cursor: se-resize;
}
.resize-handle-bl {width: 10px;height: 10px;position: absolute;bottom: 0;left: 0;cursor: sw-resize;
}
.resize-handle-tl {width: 10px;height: 10px;position: absolute;top: 0;left: 0;cursor: nw-resize;
}
.resize-handle-tr {width: 10px;height: 10px;position: absolute;top: 0;right: 0;cursor: ne-resize;
}
样式部分定义了四个缩放控制点的大小、位置和鼠标悬停时的光标样式。
使用方法
在 Vue 项目中使用该组件,首先确保组件已正确引入和注册。例如,在父组件的模板中:
<template><div id="app"><ScalableCardidName="myCard"width="300px"height="200px"top="100"left="100"><p>这是卡片的内容</p></ScalableCard></div>
</template><script setup>
import ScalableCard from './components/ScalableCard.vue';
</script>
在上述示例中,通过传入idName
、width
、height
、top
、left
等属性来定制卡片的初始状态,并在组件内部插入卡片内容。
总结
通过上述的实现思路、代码实现和使用方法介绍,我们可以看到这个基于 Vue 的可缩放卡片组件为前端开发中实现可交互的卡片功能提供了一个有效的解决方案。你也可以根据实际需求进一步扩展和优化该组件,以满足不同项目的需求。希望本文能对大家有所帮助。
相关文章:

基于 Vue 的拖拽缩放卡片组件:实现思路、方法及使用指南
引言 在前端开发中,实现可交互的组件能够极大地提升用户体验。本文将介绍一个基于 Vue 封装的可缩放卡片组件,从实现思路、代码具体实现以及使用方法等方面进行详细阐述,帮助开发者更好地理解和运用这一组件。项目源码地址:https…...

nginx 实现 正向代理、反向代理 、SSL(证书配置)、负载均衡 、虚拟域名 ,使用其他中间件监控
我们可以详细地配置 Nginx 来实现正向代理、反向代理、SSL、负载均衡和虚拟域名。同时,我会介绍如何使用一些中间件来监控 Nginx 的状态和性能。 1. 安装 Nginx 如果你还没有安装 Nginx,可以通过以下命令进行安装(以 Ubuntu 为例࿰…...

Kafka客户端-“远程主机强迫关闭了一个现有的连接”故障排查及解决
Kafka客户端-“远程主机强迫关闭了一个现有的连接”故障排查及解决 1. 故障现象 Kafka客户端发送数据时,出现“远程主机强迫关闭了一个现有的连接”错误,导致数据发送失败。错误信息如下: 2. 故障排查 【1】. 查看服务网络状态 出现故障…...

Node.js - Express框架
1. 介绍 Express 是一个基于 Node.js 的 Web 应用程序框架,主要用于快速、简便地构建 Web 应用程序 和 API。它是目前最流行的 Node.js Web 框架之一,具有轻量级、灵活和功能丰富的特点。 核心概念包括路由,中间件,请求与响应&a…...

AWS Lambda
AWS Lambda 是 Amazon Web Services(AWS)提供的无服务器计算服务,它让开发者能够运行代码而不需要管理服务器或基础设施。AWS Lambda 会自动处理代码的执行、扩展和计费,开发者只需关注编写和部署代码,而无需担心底层硬…...
mysql 如何快速删除表数据
在数据库管理中, 经常会遇到需要删除大量数据的情况. 对于 MySQL 数据库而言, 如何高效快速地删除数据是一个值得深入探讨的问题. 本文将详细介绍几种在 MySQL 中快速删除数据的方法及相关注意事项. delete 语句 delete 语句可以删除符合条件的指定数据, 但是在删除大量数据…...

物联网网关Web服务器--lighttpd服务器部署与应用测试
以下是在国产ARM处理器E2000飞腾派开发板上部署 lighttpd 并进行 CGI 应用开发的步骤: 1、lighttpd简介 Lighttpd 是一款轻量级的开源 Web 服务器软件,具有以下特点和功能: 特点 轻量级:Lighttpd 在设计上注重轻量级和高效性&a…...

vmware虚拟机配置ubuntu 18.04(20.04)静态IP地址
VMware版本 :VMware Workstation 17 Pro ubuntu版本:ubuntu-18.04.4-desktop-amd64 主机环境 win11 1. 修改 VMware虚拟网络编辑器 打开vmware,点击顶部的“编辑"菜单,打开 ”虚拟化网络编辑器“ 。 选择更改设置&#…...

智能家居篇 一、Win10 VM虚拟机安装 Home Assistant 手把手教学
智能家居篇 一、Win10 VM虚拟机安装 Home Assistant 手把手教学 文章目录 [智能家居篇]( )一、Win10 VM虚拟机安装 Home Assistant 手把手教学 前言一.下载Vm版本的HomeAsistant安装包 二.打开Vmware选择新建虚拟机1.选择自定义高级2.选择16.x及以上3.选择稍后安装4.根据官网的…...

Flutter插件制作、本地/远程依赖及缓存机制深入剖析(原创-附源码)
Flutter插件在开发Flutter项目的过程中扮演着重要的角色,我们从 https://pub.dev 上下载添加到项目中的第三方库都是以包或者插件的形式引入到代码中的,这些第三方工具极大的提高了开发效率。 深入的了解插件的制作、发布、工作原理和缓存机…...
Python猜数小游戏
Python 实现的《猜数游戏》 介绍 本文将展示如何使用 Python 编写一个简单的《猜数游戏》。这个游戏将会生成一个1到10之间的随机数,用户有最多三次机会来猜测正确的数字。如果用户猜对了,游戏将结束并显示恭喜信息;如果没有猜对࿰…...

--- 用java实现一个计时器 ---
这里的计时器值得是当线程设定的时间过了之后,自动执行该线程的工作 设计 MyTimer 既然是要在指定的时间之后执行任务,那么传入的参数就应该有run方法(需要执行的任务),time(在多少时间之后执行ÿ…...
OPI4A,目标检测,口罩检测,mnn,YoloX
记得之前,使用了bubbling导师复现的python版yolox,训练了自建的口罩数据集,得到了h5文件,又转换成pb文件,再使用阿里巴巴的MNN,使用它的MNNConvert,转换成mnn文件 最终实现了,在树莓…...
C#与Vue2上传下载Excel文件
1、上传文件流程:先上传文件,上传成功,返回文件名与url,然后再次发起请求保存文件名和url到数据库 前端Vue2代码: 使用element的el-upload组件,action值为后端接收文件接口,headers携带session信…...

Linux(Centos7)安装Mysql/Redis/MinIO
安装Mysql 安装Redis 搜索Redis最先版本所在的在线安装yum库 查看以上两个组件是否是开机自启 安装MinIO 开源的对象存储服务,存储非结构化数据,兼容亚马逊S3协议。 minio --help #查询命令帮助minio --server --help #查询--server帮助minio serve…...

港科夜闻 | 香港科大与微软亚洲研究院签署战略合作备忘录,推动医学健康教育及科研协作...
关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与微软亚洲研究院签署战略合作备忘录,推动医学健康教育及科研协作。根据备忘录,双方将结合各自于科研领域的优势,携手推动医学健康领域的交流与合作。合作方向将涵盖人才培训、…...

森林网络部署,工业4G路由器实现林区组网远程监控
在广袤无垠的林区,每一片树叶的摇曳、每一丝空气的流动,都关乎着生态的平衡与安宁。林区监控正以强大的力量,为这片绿色家园筑起一道坚固的防线。 工业 4G 路由器作为林区监控组网的守护者,凭借着卓越的通讯性能,突破…...

ASP.NET Core - 配置系统之自定义配置提供程序
ASP.NET Core - 配置系统之自定义配置提供程序 4. 自定义配置提供程序IConfigurationSourceIConfigurationProvider 4. 自定义配置提供程序 在 .NET Core 配置系统中封装一个配置提供程序关键在于提供相应的 IconfigurationSource 实现和 IConfigurationProvider 接口实现&…...
npm、yarn、pnpm包安装器差异性对比
特性npmyarnpnpm发布年份2010 年发布2016 年发布2017 年发布安装速度较慢(旧版本),但自 npm 5 后有所改善较快,尤其是在缓存方面极快,使用硬链接和全局缓存来提高速度包管理模式扁平化依赖,可能会发生重复依…...
正点原子repo放到自己的git服务器
atk-rk3568_android11 导出project-objects对应仓库 .repo/repo/repo list -n > project-object.txt将project-object.txt格式化,并通过gitolite.conf创建对应仓库 atk-rk3568_android11_repo atk-rk3568_android11/RKTools atk-rk3568_android11_repo atk-…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...