【vue】聊一聊拖拽改变DOM大小的实现
背景
大家或多或少应该有遇到过通过拖拽改变DOM大小的需求:比如说页面的侧边栏支持拖拽调整大小,使内容可视区变大。之所以讲这个需求,是因为在我个人开发的开源项目中有做到这个需求,所以在这里和大家聊一聊我的实现。以下内容均以向右拖拽为例。
思路
既然要实现拖拽,那就需要在DOM内有一个支持拖拽的节点,然后通过addEventListener
来监听鼠标点击
、鼠标点击后移动事件
、鼠标松开事件
。在鼠标点击后监听移动事件,在移动事件中计算鼠标点击位置与移动后的位置间的距离:这个距离就是DOM需要增加或者减少的宽度。
简单实现
思路清楚了,剩下的就是实现了,最主要的就是计算的部分了。贴上实现代码:
<template>
<div class="container">
<div>
<div ref="box" class="box">
<div ref="drag" class="drag"></div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue"
const box = ref()
const drag = ref()
const width = ref()
const downPageX = ref()
const moveHandle = (event) => {
document.documentElement.style.cursor = 'col-resize'
const value = event.pageX - downPageX.value
const size = width.value + value
box.value.style.width = size + 'px'
}
const upHandle = () => {
// 移除监听
document.removeEventListener('mousemove', moveHandle)
document.removeEventListener('mouseup', upHandle)
// 移除鼠标样式
document.documentElement.style.cursor = ''
}
const downListener = () => {
drag.value.addEventListener('mousedown', (event) => {
// 获取水平参数
width.value = box.value.offsetWidth
downPageX.value = event.pageX
// 添加鼠标移动事件
document.addEventListener('mousemove', moveHandle)
// 添加鼠标松开事件
document.addEventListener('mouseup', upHandle)
})
}
onMounted(() => {
downListener()
})
onBeforeUnmount(() => {
// 移除监听
document.removeEventListener('mousemove', moveHandle)
document.removeEventListener('mouseup', upHandle)
// 移除鼠标样式
document.documentElement.style.cursor = ''
})
</script>
<style scoped>
// 省略...
</style>
拖拽指令
上面已经完成了简单的实现,但是在我们实际开发过程中不会就某一个地方需要用到这个实现。虽然可以CV
,但这绝对不是最优解,在这基础之上进一步优化。
我们可以将这个功能封装成自定义指令,思路与上文的思路相差无几。在所有节点都挂载完成之后,在使用了指令的节点里面插入一个可拖拽的节点,然后通过addEventListener
来监听各个鼠标事件,再进行一些计算,最后在节点卸载后移除相关监听。
本着精益求精的态度,必然不能只做到上面提到的那些。在实际开发中或许会遇到一些其它情况:比如我们拖拽的方向、可拖拽的最大最小距离、具体事件的回调等等问题都需要考虑在内。就有了以下完整的实现:
/**
* @description: 拖拽改变大小指令
* @param {*}
* @return {*}
* @author: gumingchen
*/
const horizontal = ['left', 'right']
const vertical = ['top', 'bottom']
const positions = [...horizontal, ...vertical]
// 默认参数
const defaultOptions = {
// 拖拽位置
position: positions[1],
// 拖拽区域大小
areaSize: '3px',
// 拖拽区域背景色
areaBackground: 'transparent',
// Element的最小宽度/高度
min: 200,
// Element的最大宽度/高度
max: 460,
// 处理器
downHandler: null,
moveHandler: null,
upHandler: null
}
// 初始化数据
let data = {
el: null,
dom: null,
options: null,
width: null,
downPageX: null,
height: null,
downPageY: null
}
/**
* 拖拽区域位置处理
* @param {*} div
* @param {*} position
*/
const positionHandle = (div, position, size) => {
// 水平方向
if (horizontal.includes(position)) {
div.style.top = '0px'
div.style.bottom = '0px'
div.style[position] = '0px'
div.style.cursor = 'col-resize'
div.style.width = size
}
// 垂直方向
if (vertical.includes(position)) {
div.style.right = '0px'
div.style.left = '0px'
div.style[position] = '0px'
div.style.cursor = 'row-resize'
div.style.height = size
}
}
/**
* 创建节点
* @returns Element
*/
const createElement = (options) => {
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.userSelect = 'none'
const { position, areaSize, areaBackground } = options
positionHandle(div, position, areaSize)
div.style.background = areaBackground
return div
}
/**
* 鼠标移动监听事件
* @param {*} event
* @returns
*/
const moveHandle = (event) => {
const { el, dom, options, width, downPageX, height, downPageY } = data
const { moveHandler, position, max, min } = options
// 事件回调
if (typeof moveHandler === 'function') {
moveHandler(event)
}
// 设置鼠标样式
document.documentElement.style.cursor = dom.style.cursor
// 水平方向处理
if (horizontal.includes(position)) {
const value = position === horizontal[0] ? downPageX - event.pageX : event.pageX - downPageX
const size = width + value
if (size >= max) {
return
}
if (size <= min) {
return
}
el.style.width = size + 'px'
}
// 垂直方向处理
if (vertical.includes(position)) {
const value = position === vertical[0] ? downPageY - event.pageY : event.pageY - downPageY
const size = height + value
if (size >= max) {
return
}
if (size <= min) {
return
}
el.style.height = size + 'px'
}
}
/**
* 鼠标松开监听事件
* @param {*} event
*/
const upHandle = (event) => {
// 事件回调
const { upHandler } = data.options
if (typeof upHandler === 'function') {
upHandler(event)
}
// 移除监听
document.removeEventListener('mousemove', moveHandle)
document.removeEventListener('mouseup', upHandle)
// 移除鼠标样式
document.documentElement.style.cursor = ''
}
/**
* 鼠标按下事件
* @param {*} el 当前节点
* @param {*} dom 可拖拽节点
* @param {*} options 参数
*/
const downListener = (el, dom, options) => {
dom.addEventListener('mousedown', (event) => {
// 事件回调
const { downHandler } = options
if (typeof downHandler === 'function') {
downHandler(event)
}
// 获取水平参数
const width = el.offsetWidth
const downPageX = event.pageX
// 获取垂直参数
const height = el.offsetHeight
const downPageY = event.pageY
// 设置数据
data = { el, dom, options, width, downPageX, height, downPageY }
// 添加鼠标移动事件
document.addEventListener('mousemove', moveHandle)
// 添加鼠标松开事件
document.addEventListener('mouseup', upHandle)
})
}
export default {
mounted(el, binding) {
el.style.position = 'relative'
const { arg, value } = binding
const options = value ? { ...defaultOptions, ...value } : { ...defaultOptions }
if (arg) {
options.position = arg
}
if (!positions.includes(options.position)) {
console.warn(`[Directive warn]: Invalid arg: validation failed for arg. Expected one of ${ JSON.stringify(positions) }, got value "${ options.position }".`)
return
}
const dom = createElement(options)
downListener(el, dom, options)
el.appendChild(dom)
},
unmounted() {
// 移除监听
document.removeEventListener('mousemove', moveHandle)
document.removeEventListener('mouseup', upHandle)
// 移除鼠标样式
document.documentElement.style.cursor = ''
}
}
以上就是本次分享的内容,附上源码。
感谢看官看到这里,如果觉得文章不错的话,可以给小生的几个开源项目点个Star⭐!
- 基于 Vue3 + Element-plus 管理后台基础功能框架
- 预览:admin.gumingchen.icu
- Github:github.com/gmingchen/a…
- Gitee:gitee.com/shychen/agi…
- 基础版后端:github.com/gmingchen/j…
- 文档:admin.gumingchen.icu/doc/
- 基于 Vue3 + Element-plus + websocket 即时聊天系统
- 基于 node 开发的后端服务:github.com/gmingchen/n…
转载自:https://juejin.cn/post/7281113851714781199