基于 Vue 的拖拽缩放卡片组件:实现思路、方法及使用指南

ops/2025/1/17 15:42:32/

引言

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

实现思路

  1. 定位与布局:通过position: absolute对卡片进行定位,利用lefttoprightbottom属性确定其在页面中的位置,同时设置widthheight来定义卡片的初始大小。
  2. 缩放控制点:在卡片的四个角(左上角、右上角、左下角、右下角)添加可交互的缩放控制点,通过监听这些控制点的鼠标事件(mousedownmousemovemouseup)来实现卡片的缩放功能。
  3. 状态跟踪:使用变量来记录卡片的初始大小、位置以及鼠标的初始位置,在缩放过程中根据鼠标的移动距离计算卡片新的大小和位置。

代码实现

模板部分(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通过idstyle绑定来设置卡片的位置和大小。slot用于插入卡片的内容,四个角的div分别代表缩放控制点,通过动态绑定类名来标识不同的控制点。

script 部分(script setup)

javascript">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);
};
  1. 属性定义:通过defineProps定义组件接受的属性,包括idName(必选,用于唯一标识卡片)、widthheighttopleftbottomright,并设置了默认值。
  2. 变量初始化:声明了一系列变量用于跟踪卡片的初始状态和缩放过程中的状态。
  3. 生命周期钩子:在onMounted钩子函数中,获取卡片元素和缩放控制点元素,并为每个缩放控制点添加mousedown事件监听器。当鼠标按下时,记录卡片的初始大小和鼠标位置,同时添加mousemovemouseup事件监听器。
  4. 缩放函数resize函数根据鼠标移动的距离和缩放控制点的类型来计算并更新卡片的大小和位置。
  5. 停止缩放函数stopResize函数在鼠标松开时,移除mousemovemouseup事件监听器,并更新卡片位置的累计偏移量。

样式部分(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 项目中使用该组件,首先确保组件已正确引入和注册。例如,在父组件的模板中:

javascript"><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>

在上述示例中,通过传入idNamewidthheighttopleft等属性来定制卡片的初始状态,并在组件内部插入卡片内容。

总结

通过上述的实现思路、代码实现和使用方法介绍,我们可以看到这个基于 Vue 的可缩放卡片组件为前端开发中实现可交互的卡片功能提供了一个有效的解决方案。你也可以根据实际需求进一步扩展和优化该组件,以满足不同项目的需求。希望本文能对大家有所帮助。


http://www.ppmy.cn/ops/150840.html

相关文章

如何在 ASP.NET Core 中实现速率限制?

在 ASP.NET Core 中实现速率限制&#xff08;Rate Limiting&#xff09;中间件可以帮助你控制客户端对 API 的请求频率&#xff0c;防止滥用和过载。速率限制通常用于保护服务器资源&#xff0c;确保服务的稳定性和可用性。 ASP.NET Core 本身并没有内置的速率限制中间件&…

CORBA等一些主流的软件构件标准

1. CORBA(Common Object Request Broker Architecture) 简介: CORBA是由OMG(Object Management Group)制定的分布式对象标准,旨在支持异构系统之间的互操作性。它允许不同语言和平台编写的对象相互通信。 核心组成: ORB(Object Request Broker): 提供分布式对象调用的核…

Visual Studio Code (VSCode)为当前项目设置保存时自动格式化

在 Visual Studio Code (VSCode) 中&#xff0c;你可以为单个项目设置特定的配置&#xff0c;而不会影响全局设置。这可以通过创建项目级别的设置文件来实现。以下是具体步骤&#xff1a; 为当前项目设置保存时自动格式化 打开命令面板&#xff1a; 使用快捷键 CtrlShiftP&…

2.5G交换机 TL-SE2420 简单开箱评测,16个2.5G电口+4个10G光口(SFP+)

TPLINK&#xff08;普联&#xff09;的万兆上联的2.5G网管交换机TL-SE2420简单开箱测评。16个2.5G电口&#xff0c;4个万兆SFP口。 买来替换原先的TL-SH5428&#xff08;24千兆4万兆&#xff09;。 TL-SH5428 万兆交换机开箱和简单的评测&#xff1a;https://blog.zeruns.com…

Golang—— error 和 panic

本文详细介绍Golang的两种错误处理机制&#xff1a;error 和 panic。 文章目录 Golang 的错误处理机制概述error特点代码示例基本用法创建 error panic特点运行时错误示例defer 和 recover 的结合使用代码示例基本用法创建 panic panic 的执行机制 error 和 panic 的对比生产环…

LabVIEW 程序中的 R6025 错误

R6025错误 通常是 运行时库 错误&#xff0c;特别是与 C 运行时库 相关。这种错误通常会在程序运行时出现&#xff0c;尤其是在使用 C 编译的程序或依赖 C 运行时库的程序时。 ​ 可能的原因&#xff1a; 内存访问冲突&#xff1a; R6025 错误通常是由于程序在运行时访问无效内…

【ARM】MDK如何将变量存储到指定内存地址

1、 文档目标 通过MDK的工程配置&#xff0c;将指定的变量存储到指定的内存地址上。 2、 问题场景 在项目工程的开发过程中&#xff0c;对于flash要进行分区&#xff0c;需要规划出一个特定的内存区域来存储变量。 3、软硬件环境 1&#xff09;、软件版本&#xff1a;MDK 5.…

掌握C语言内存布局:数据存储的智慧之旅

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 引言正文一、数据类型介绍1.内置类型2.自定义…