基于el-drawer组件封装拖拽、缩放功能

功能描述:基于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弹框封装共用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值