功能描述:基于vue封装全局自定义指令,满足弹框组件拖拽和缩放大小功能。
1. 生成drawerDrag.js文件
import Vue from 'vue'
// v-drawerDrag: 弹框拖拽+水平方向伸缩+对角线拉伸
Vue.directive('drawerDrag', {
// el为指令绑定dom
bind(el) {
// drawerHeaderEl为标题栏,绑定mousedown事件进行拖拽
const drawerHeaderEl = el.querySelector('.el-drawer__header')
// dragDom为指令绑定的dom元素,定义元素便于区分
const dragDom = el.querySelector('.el-drawer')
if (!dragDom) {
return
}
// 获取css所有属性兼容性写法 ie dom元素.currentStyle 火狐谷歌 Window.getComputedStyle(dom元素, null)
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
// 定义鼠标按下事件
const moveDown = e => {
// e.clientX/e.clientY: 鼠标相对于浏览器可视窗口的X,Y坐标
// offsetLeft/offsetTop: 当前元素相对于其offsetParent元素的顶部,左边的距离,这里title无定位偏移,故为0
// 获取元素相对位置
const disX = e.clientX - drawerHeaderEl.offsetLeft
const disY = e.clientY - drawerHeaderEl.offsetTop
// 获取页面可视区宽度、高度兼容写法
const screenWidth = document.documentElement.clientWidth || document.body.clientWidth
const screenHeight = document.documentElement.clientHeight || document.body.clientHeight
// 获取对话框宽度、高度
const dragDomWidth = dragDom.offsetWidth
const dragDomHeight = dragDom.offsetHeight
// 获取对话框边界最小、最大的left值
const minDragDomLeft = dragDom.offsetLeft
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
// 获取对话框边界最小、最大top值
const minDragDomTop = dragDom.offsetTop
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
// 获取到的值带px在下面需要用正则匹配替换
let styL = sty.left
if (styL === 'auto') styL = '0px' // 为兼容ie写法
let styT = sty.top
// 需要注意在ie中 第一次获取到的值为组件自带50% 移动后赋值为px
if (sty.left.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100)
styT = +document.body.clientHeight * (+styT.replace(/%/g, '') / 100)
} else {
styL = +styL.replace(/\px/g, '')
styT = +styT.replace(/\px/g, '')
}
// 鼠标移动事件
document.onmousemove = function (e) {
el.style.pointerEvents = 'auto' // 层叠样式表css,不穿透
// 通过事件委托,计算移动的距离
let left = e.clientX - disX
let top = e.clientY - disY
// 边界左侧处理
if (-(left) > minDragDomLeft) {
left = -(minDragDomLeft)
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft
}
// 边界顶部处理
if (-(top) > minDragDomTop) {
top = -(minDragDomTop)
} else if (top > maxDragDomTop) {
top = maxDragDomTop
}
// 移动当前元素
dragDom.style.left = `${left + styL}px`
dragDom.style.top = `${top + styT}px`
// 将此时移动的位置传出去
// binding.value({ x: e.pageX, y: e.pageY })
}
// 鼠标抬起停止弹窗移动
document.onmouseup = function () {
el.style.pointerEvents = 'none' // 层叠样式表css,穿透
document.onmousemove = null
document.onmouseup = null
}
}
drawerHeaderEl.onmousedown = moveDown
// 定义鼠标悬停样式
const CURSORTYPE = {
top: 'n-resize', // 边缘可被向上移动
bottom: 's-resize', // 边缘可被向下移动
left: 'w-resize', // 边缘可被向左移动
right: 'e-resize', // 边缘可被向右移动
right_top: 'ne-resize', // 边缘可被向上及向右移动
left_top: 'nw-resize', // 边缘可被向上及向左移动
left_bottom: 'sw-resize', // 光标边缘可被向下及向左移动
right_bottom: 'se-resize', // 光标边缘可被向下及向右移动
default: 'default', // 默认光标
}
// 判断鼠标悬浮指针类型
const checkType = obj => {
const { x, y, left, top, width, height } = obj
let type
if (x > left + width - 5 && el.scrollTop + y <= top + height - 5 && top + 5 <= y) {
type = 'right'
} else if (left + 5 > x && el.scrollTop + y <= top + height - 5 && top + 5 <= y) {
type = 'left'
} else if (el.scrollTop + y > top + height - 5 && x <= left + width - 5 && left + 5 <= x) {
type = 'bottom'
} else if (top + 5 > y && x <= left + width - 5 && left + 5 <= x) {
type = 'top'
} else if (x > left + width - 5 && el.scrollTop + y > top + height - 5) {
type = 'right_bottom'
} else if (left + 5 > x && el.scrollTop + y > top + height - 5) {
type = 'left_bottom'
} else if (top + 5 > y && x > left + width - 5) {
type = 'right_top'
} else if (top + 5 > y && left + 5 > x) {
type = 'left_top'
}
return type || 'default'
}
// 判断边界条件
const boundaryLimit = obj => {
const { left, top, width, height, diffX, diffY, screenHeight, screenWidth, arr } = obj
if (arr[0] == 'right' || arr[1] == 'right') {
if (width + diffX > screenWidth - left) {
dragDom.style.width = screenWidth - left + 'px'
} else {
dragDom.style.width = width + diffX + 'px'
}
}
if (arr[0] == 'left' || arr[1] == 'left') {
if (width - diffX > width + left) {
dragDom.style.width = width + left + 'px'
dragDom.style.left = -parseInt(sty.marginLeft) + 'px'
} else {
dragDom.style.width = width - diffX + 'px'
// left实际值 = left + marginLeft 计算时需要将marginLeft减掉
dragDom.style.left = left + diffX - parseInt(sty.marginLeft) + 'px'
}
}
if (arr[0] == 'top' || arr[1] == 'top') {
if (height - diffY > height + top) {
dragDom.style.height = height + top + 'px'
dragDom.style.top = -parseInt(sty.marginTop) + 'px'
} else {
dragDom.style.height = height - diffY + 'px'
// top实际值 = top + marginTop 计算时需要将marginTop减掉
dragDom.style.top = top + diffY - parseInt(sty.marginTop) + 'px'
}
}
if (arr[0] == 'bottom' || arr[1] == 'bottom') {
if (height + diffY > screenHeight - top) {
dragDom.style.height = screenHeight - top
} else {
dragDom.style.height = height + diffY + 'px'
}
}
}
// 缩放鼠标移动事件
dragDom.onmousemove = e => {
const x = e.clientX
const y = e.clientY
const left = dragDom.offsetLeft
const top = dragDom.offsetTop
const width = dragDom.clientWidth
const height = dragDom.clientHeight
let type = checkType({ x, y, left, top, width, height })
dragDom.style.cursor = CURSORTYPE[type] || 'default'
}
// 缩放鼠标点击事件
dragDom.onmousedown = e => {
const x = e.clientX
const y = e.clientY
const width = dragDom.clientWidth
const height = dragDom.clientHeight
const left = dragDom.offsetLeft
const top = dragDom.offsetTop
const screenWidth = document.documentElement.clientWidth || document.body.clientWidth
const screenHeight = document.documentElement.clientHeight || document.body.clientHeight
let type = checkType({ x, y, left, top, width, height })
// 判断是否为弹框头部
if (x > left && x < left + width && y > top + 5 && y < top + drawerHeaderEl.clientHeight) {
// console.log('走拖拽功能')
} else {
// 拉伸开始
document.onmousemove = function (e) {
// 移动时禁用默认事件
e.preventDefault()
let endX = e.clientX
let endY = e.clientY
let diffX = endX - x
let disY = endY - y
let arr
// 将type转换为数组格式,简化代码判断调用
if (type.indexOf('_') == -1) {
arr = [type, '']
} else {
arr = type.split('_')
}
boundaryLimit({ left, top, width, height, diffX, diffY, screenHeight, screenWidth, arr })
}
// 拉伸结束
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
}
}
}
}
})
2.main.js引入
// 全局引入
import '@/utils/drawerDrag.js'
3.页面使用自定义指令
// v-drawerDrag 直接为自定义指令
<template>
<el-drawer
title="标题"
:wrapperClosable="false"
:modal="false"
v-drawerDrag
>
<span>页面内容展示</span>
</el-drawer>
</template>
同理el-dialog组件也可直接复用,只需要修改js中获取的class为el-dialog对应的class
此封装指令可跟另一篇el-drawer弹框封装共用