VUE3拖拽、缩放的简单实现
在现代Web应用中,拖拽和缩放功能是非常常见的交互方式。这些功能可以增强用户体验,使得用户能够更加直观地操作页面元素。本文将详细介绍如何使用Vue.js来实现一个简单的可拖拽、可缩放、可移动的组件。
项目背景
我们希望创建一个可以在父容器内自由移动和调整大小的子元素。这个子元素可以通过点击并拖动其边缘来进行尺寸调整,并且可以通过点击并拖动其主体部分来进行位置移动。
实现思路
- HTML结构:定义一个包含拖拽按钮的可移动区域。
- CSS样式:设置父容器和子元素的样式,使其具有透明背景和模糊效果,看起来像是悬浮在空中。
- JavaScript逻辑:
- 监听鼠标按下事件,开始拖拽或缩放。
- 根据鼠标的移动更新元素的位置或尺寸。
- 监听鼠标抬起事件,结束拖拽或缩放。
详细步骤
1. HTML结构
首先,在模板中定义了一个主容器.main,其中包含一个可拖拽的玻璃窗格.glass。在这个玻璃窗格内有一个可调整大小和移动的.drag-item,并且在其四周分布着八个用于调整大小的按钮.drag-btn。
<template>
<div class="main">
<div class="glass" ref="dragWrapRef">
<div class="drag-item" ref="dragItemRef" @mousedown="startDrag" :style="style">
<div
v-for="(type, index) in dragType"
:key="index"
class="drag-btn"
:class="type"
@mousedown.stop="startMove($event, type)"
></div>
</div>
</div>
</div>
</template>
2. CSS样式
通过CSS设置了父容器和子元素的样式,包括背景颜色、模糊效果、圆角等。同时为每个调整大小的按钮设置了不同的定位属性,以便它们位于子元素的四个角落和四条边的中间。
<style lang="less" scoped>
.main {
width: 100vw;
height: 100vh;
background: url('@/assets/images/background.png') no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
align-items: center;
}
.glass {
width: 80%;
height: 80%;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 10px;
position: relative;
}
.drag-item {
width: 300px;
height: 360px;
position: absolute;
background: rgba(224, 164, 164, 0.2);
backdrop-filter: blur(10px);
border-radius: 10px;
cursor: grab;
}
.drag-btn {
width: 10px;
height: 10px;
background-color: transparent;
border: none;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
cursor: move;
}
.lt {
top: 0;
left: 0;
}
.lc {
top: 50%;
left: 0;
transform: translateY(-50%);
}
.lb {
bottom: 0;
left: 0;
}
.ct {
top: 0;
left: 50%;
transform: translateX(-50%);
}
.cb {
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.rt {
top: 0;
right: 0;
}
.rc {
top: 50%;
right: 0;
transform: translateY(-50%);
}
.rb {
bottom: 0;
right: 0;
}
</style>
3. JavaScript逻辑
引入依赖
使用Vue Composition API (setup语法糖),引入所需的钩子函数和其他工具函数。
<script setup>
import { ref, onMounted, computed, onUnmounted } from 'vue'
</script>
定义数据
定义了一些响应式变量来存储状态信息,例如当前的拖拽类型、初始位置和尺寸、最大最小尺寸限制等。
const dragType = ['lt', 'lc', 'lb', 'ct', 'cb', 'rt', 'rc', 'rb']
const dragWrapRef = ref(null)
const dragItemRef = ref(null)
const dragWrapRectRef = ref(null)
const resizeType = ref('')
const initialPositionX = ref(0) // 初始X位置
const initialPositionY = ref(0) // 初始Y位置
const initialMouseX = ref(0) // 初始鼠标X位置
const initialMouseY = ref(0) // 初始鼠标Y位置
const intiialWidth = ref(300) // 初始宽度
const intiialHeight = ref(360) // 初始高度
let minHeight = 360 // 最小高度
let minWidth = 300 // 最小宽度
let maxWidth = 500 // 最大宽度
let maxHeight = 600 // 最大高度
let dragBoxX = 0, // 拖动框X位置
dragBoxY = 0 // 拖动框Y位置
let dragBoxWidth = 0, // 拖动框宽度
dragBoxHeight = 0 // 拖动框高度
计算属性
计算出子元素的最终样式,包括位置和尺寸。
const style = computed(() => {
return {
top: initialPositionY.value + 'px',
left: initialPositionX.value + 'px',
width: intiialWidth.value + 'px',
height: intiialHeight.value + 'px',
}
})
处理拖拽事件
定义了三个主要的处理函数:handleDrag用于处理整体拖拽移动,handleResize用于处理各个方向上的尺寸调整,startDrag和startMove分别对应启动这两种行为,而stopDrag则用于终止所有相关的事件监听器。
/**
* 处理拖动事件
* @param {MouseEvent} e - 鼠标事件对象
*/
function handleDrag(e) {
let deltaX = e.clientX - initialMouseX.value
let deltaY = e.clientY - initialMouseY.value
let newX = initialPositionX.value + deltaX
let newY = initialPositionY.value + deltaY
if (isMouseInsideElement(newX, newY)) {
initialPositionX.value = newX
initialPositionY.value = newY
initialMouseX.value = e.clientX
initialMouseY.value = e.clientY
}
}
/**
* 开始拖动事件
* @param {MouseEvent} e - 鼠标事件对象
*/
function startDrag(e) {
initialMouseX.value = e.clientX
initialMouseY.value = e.clientY
document.addEventListener('mousemove', handleDrag)
document.addEventListener('mouseup', stopDrag)
}
/**
* 处理调整大小事件
* @param {MouseEvent} e - 鼠标事件对象
*/
function handleResize(e) {
let deltaX = e.clientX - initialMouseX.value
let deltaY = e.clientY - initialMouseY.value
let newWidth = dragBoxWidth
let newHeight = dragBoxHeight
switch (resizeType.value) {
case 'lt':
newWidth -= deltaX
if (newWidth < maxWidth && newWidth > minWidth) {
intiialWidth.value = newWidth
initialPositionX.value = dragBoxX + deltaX
}
newHeight -= deltaY
if (newHeight < maxHeight && newHeight > minHeight) {
intiialHeight.value = newHeight
initialPositionY.value = dragBoxY + deltaY
}
break
case 'lc':
newWidth -= deltaX
if (newWidth < maxWidth && newWidth > minWidth) {
intiialWidth.value = newWidth
initialPositionX.value = dragBoxX + deltaX
}
break
case 'lb':
newWidth -= deltaX
if (newWidth < maxWidth && newWidth > minWidth) {
intiialWidth.value = newWidth
initialPositionX.value = dragBoxX + deltaX
}
newHeight += deltaY
if (newHeight < maxHeight && newHeight > minHeight) {
intiialHeight.value = newHeight
}
break
case 'ct':
newHeight -= deltaY
if (newHeight < maxHeight && newHeight > minHeight) {
intiialHeight.value = newHeight
initialPositionY.value = dragBoxY + deltaY
}
break
case 'cb':
newHeight += deltaY
if (newHeight < maxHeight && newHeight > minHeight) {
intiialHeight.value = newHeight
}
break
case 'rt':
newWidth += deltaX
if (newWidth < maxWidth && newWidth > minWidth) {
intiialWidth.value = newWidth
}
newHeight -= deltaY
if (newHeight < maxHeight && newHeight > minHeight) {
intiialHeight.value = newHeight
initialPositionY.value = dragBoxY + deltaY
}
break
case 'rc':
newWidth += deltaX
if (newWidth < maxWidth && newWidth > minWidth) {
intiialWidth.value = newWidth
}
break
case 'rb':
newWidth += deltaX
if (newWidth < maxWidth && newWidth > minWidth) {
intiialWidth.value = newWidth
}
newHeight += deltaY
if (newHeight < maxHeight && newHeight > minHeight) {
intiialHeight.value = newHeight
}
break
}
}
/**
* 开始调整大小事件
* @param {MouseEvent} e - 鼠标事件对象
* @param {string} type - 调整大小的类型
*/
function startMove(e, type) {
initialMouseX.value = e.clientX
initialMouseY.value = e.clientY
resizeType.value = type
dragBoxX = initialPositionX.value
dragBoxY = initialPositionY.value
dragBoxWidth = intiialWidth.value
dragBoxHeight = intiialHeight.value
document.addEventListener('mousemove', handleResize)
document.addEventListener('mouseup', stopDrag)
}
/**
* 停止拖动或调整大小事件
*/
function stopDrag() {
document.removeEventListener('mousemove', handleDrag)
document.removeEventListener('mousemove', handleResize)
document.removeEventListener('mouseup', stopDrag)
}
辅助函数
isMouseInsideElement函数用于检查新位置是否超出了父容器的边界,防止子元素移出可视范围。
/**
* 检查鼠标是否在元素内部
* @param {number} newX - 新的X坐标
* @param {number} newY - 新的Y坐标
* @returns {boolean} - 鼠标是否在元素内部
*/
function isMouseInsideElement(newX, newY) {
return (
newX >= 0 &&
newX + intiialWidth.value <= dragWrapRectRef.value.width &&
newY >= 0 &&
newY + intiialHeight.value <= dragWrapRectRef.value.height
)
}
生命周期钩子
在组件挂载后获取必要的DOM信息,并在卸载前清理所有的事件监听器以避免内存泄漏。
onMounted(() => {
intiialWidth.value = dragItemRef.value.clientWidth
intiialHeight.value = dragItemRef.value.clientHeight
initialPositionX.value = dragItemRef.value.offsetLeft
initialPositionY.value = dragItemRef.value.offsetTop
dragWrapRectRef.value = dragWrapRef.value.getBoundingClientRect()
maxWidth = dragWrapRectRef.value.width
maxHeight = dragWrapRectRef.value.height
})
onUnmounted(() => {
stopDrag()
})
结论
通过上述代码的编写,我们可以轻松地实现一个支持拖拽、缩放和移动功能的Vue组件。这个组件不仅能够满足基本的需求,还具备良好的性能和灵活性,可以根据具体的应用场景进行进一步的定制和优化。希望这篇文章能帮助你更好地理解和掌握这类交互功能的实现方法。
完整代码示例
以下是完整的Vue单文件组件代码:
这段代码可以直接在Vue项目中使用,只需确保路径正确即可加载背景图片。希望这篇教程对你有所帮助!