Vue自定义指令实现弹窗拖拽四边拉伸及对角线拉伸效果

引言

近期公司vue前端项目需求:实现弹窗的拖拽,四边拉伸及对角线拉伸,以及弹窗边界处理。本人使用vue的自定义指令编写了drag.js文件分享给大家一起学习,以下代码是本人提取出来的示意demo,仅供参考。这是本人前端小白的第一篇技术分享,如有错误的地方,请大家批评指正!

页面布局

<template>
  <div
    class="parameter"
    v-dialogDrag
  >
    <div class="title">标题
      <div class="close">
        <img
          src="../assets/close.png"
          alt=""
        >
      </div>
    </div>
    <div class="content">内容区</div>
  </div>
</template>
<style lang="less">
.parameter {
  height: 569px;
  width: 960px;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: calc(-960px / 2);
  margin-top: calc(-569px / 2);
  z-index: 999;
  background: #fff;
  box-sizing: border-box;
  box-shadow: 0px 12px 32px 0px rgba(0, 0, 0, 0.08);
  border-radius: 2px;
  .title {
    display: flex;
    font-size: 16px;
    height: 48px;
    line-height: 48px;
    background: #f5f5f5;
    box-sizing: border-box;
    box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.12);
    border-radius: 2px 2px 0px 0px;
    padding: 0 20px;
    z-index: 99;
    font-size: 16px;
    font-weight: 500;
    color: rgba(0, 0, 0, 0.85);
    .close {
      img {
        width: 10px;
      }
      margin-left: auto; // 右对齐
    }
  }
  .content {
    display: flex;
    justify-content: center;
    align-items: center;
    height: calc(100% - 48px);
    box-sizing: border-box;
    background: #fff;
    overflow: auto;
  }
}
</style>

页面布局实际效果如下:

drag.js文件

可以在main.js全局引入drag.js文件,也可以单独在弹窗组件内部组件引入,看是否还有其他使用场景。

项目目录截图

main.js全局引入drag.js

import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue'
import '../drag.js'

Vue.config.productionTip = false
Vue.use(ElementUI);

new Vue({
  render: h => h(App),
}).$mount('#app')

弹窗拖拽实现及边界限制

import Vue from 'vue'
// v-dialogDrag: 弹窗拖拽+水平方向伸缩+对角线拉伸
Vue.directive('dialogDrag', {
  bind(el) {
  	// dialogHeaderEl为标题栏,绑定mousedown事件进行拖拽
    const dialogHeaderEl = el.querySelector('.title')
   	// dragDom为指令绑定dom元素,定义变量便于区分
    const dragDom = el
    // 获取css所有属性兼容性写法 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
    // 定义鼠标按下事件
	const moveDown = e => {
      // e.clientX,Y:鼠标相对于浏览器可视窗口的X,Y坐标
      // offsetTop,offsetLeft:当前元素相对于其offsetParent元素的顶部,左边的距离,这里title无定位偏移,故为0
      const disX = e.clientX - dialogHeaderEl.offsetLeft // 元素相对位置
      const disY = e.clientY - dialogHeaderEl.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 // 对话框高度

      const minDragDomLeft = dragDom.offsetLeft // 对话框边界最小left值
      const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth // 对话框边界最大left值

      const minDragDomTop = dragDom.offsetTop // 对话框边界最小Top值
      const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight // 对话框边界最大Top值
      // 获取到的值带px 正则匹配替换
      let styL = sty.left

      // 为兼容ie
      if (styL === 'auto') styL = '0px'
      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) {
        // 通过事件委托,计算移动的距离
        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`

	// 鼠标抬起停止弹窗移动
      document.onmouseup = function () {
        document.onmousemove = null
        document.onmouseup = null
      }
    }
    dialogHeaderEl.onmousedown = moveDown
  }
})

鼠标指针悬停样式

弹窗并没有设置cursor:move悬停样式,因为参考的是浏览器拖拽实际效果,如果想设置move,需要增加边界判断条件。

判断鼠标悬浮指针类型中x > left + width - 5,其中5为自己设置的可拉伸区域,因为需求中弹窗不可设置边框和padding,所以无实际可拖拽元素,故手动设置5px(可根据需求自行更改)。

鼠标指针悬停更多样式请参考MDN

 // 定义鼠标悬停样式
    const CURSORTYPE = {
      top: 'n-resize',
      bottom: 's-resize',
      left: 'w-resize',
      right: 'e-resize',
      // right_top写法是便于后面代码数据处理
      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'
    }

四边拉伸及对角线拉伸

在做对角线拉伸过程中思路出现一点偏差,我发现浏览器窗口对角线拉伸可以X轴方向拉伸,Y方向拉伸,斜边拉伸,故分三种情况判断,可是这样做出来实际弹窗效果只能拉伸一点点,不满足拉伸需求。经过思考后发现,实际对角线拉伸为X,Y轴叠加和,参考矢量叠加。

因为对角线拉伸为X轴和Y轴的叠加,故考虑将四边拉伸封装函数,对角线拉伸直接调用相应的X,Y轴,减少代码量。传递数据的时候因为对角线拉伸需要传递两个值,而四边拉伸只需要传递一个值,所以需要对数据进行包装。例如:右侧拉伸传递数据['right', null],而右下角传递数据['right', 'bottom']

  // 判断边界条件
    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.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
      // dragDom.style.userSelect = 'none'
      let type = checkType({ x, y, left, top, width, height })
      // 判断是否为弹窗头部
      if (x > left &&
        x < left + width &&
        y > top + 5 &&
        y < top + dialogHeaderEl.clientHeight) {
        // dialogHeaderEl.onmousedown = moveDown
      } else {
        document.onmousemove = function (e) {
          // 移动时禁用默认事件
          e.preventDefault()
          let endX = e.clientX
          let endY = e.clientY
          let diffX = endX - x
          let diffY = 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 = function () {
          document.onmousemove = null

          document.onmouseup = null
        }
      }
    }    

拉伸干涉

因为弹窗设置了overflow: auto,故拉伸过程势必会产生右侧,底部滚动条,在实际拉伸的时候滚动条会和拉伸区域干涉。解决方案为:在弹窗右侧和底部外部增加空div条,实际拉伸区域为空div即可解决。(空div条宽高为5px,与之前设置的拉伸区域一致)

<template>
  <div
    class="parameter"
    v-dialogDrag
  >
    <div class="title">标题
      <div class="close">
        <img
          src="../assets/close.png"
          alt=""
        >
      </div>
    </div>
    <div class="content">内容区</div>
    <div class="rightBlank">123</div>
    <div class="bottomBlank">456</div>
  </div>
</template>

更改后页面效果为

附项目仓库地址

到此这篇关于Vue自定义指令实现弹窗拖拽,四边拉伸及对角线拉伸的文章就介绍到这了,更多相关vue自定义指令弹窗拖拽内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue+elementui通用弹窗的实现(新增+编辑)

    本文主要介绍了vue+elementui通用弹窗的实现(新增+编辑),分享给大家,具体如下: 组件模板 <el-dialog :title="title" :visible.sync="dialogShow" :close-on-click-modal="false"> <div class="ym-common-dialog" :class="customClass"> <d

  • vue中实现点击空白区域关闭弹窗的两种方法

    1. 第一种做法 首页在外层容器里面取一个名字为main,即ref="main",当bankSwitch为true的时候,弹窗出现 <div class="selectedBorder" ref="main"> <div class="bankItem" v-if="bankSwitch == true"> 你好我是弹窗里面的内容部分 </div> </div>

  • Vue 实现一个简单的鼠标拖拽滚动效果插件

    最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了. github 地址:github.com/qq449245884- 最近在做一个新的项目,有个需要是这样的: 简单描述一下,就是鼠标拖动页面,整个页面会随着的鼠标的拖拽而移动,如果页面有内容,里面的内容也需要跟着拖动的外层整体移到. 一开始没啥思路,所以就发了个朋友圈,得到的答案挺多的,主要还是用拖拽之类的,但这个拖拽只是单个元素的拖动,我想要的整个视图的拖动. 这里线索又断了.

  • vue基于Echarts的拖拽数据可视化功能实现

    背景 我司产品提出了一个需求,做一个数据基于Echars的可拖拽缩放的数据可视化,上网百度了一番,结果出现了两种结局,一种花钱买成熟产品(公司不出钱),一种没有成熟代码,只能自己写了,故事即将开始,敬请期待.......  不,还是先上一张效果图吧,请看...... 前期知识点 1. offset(偏移量) 定义:当前元素在屏幕上占用的空间,如下图: 其中: offsetHeight: 该元素在垂直方向上的占用的空间,单位为px,不包括margin. offsetWidth:该元素在水平方向上的

  • vue自定义指令和动态路由实现权限控制

    功能概述: 根据后端返回接口,实现路由动态显示 实现按钮(HTML元素)级别权限控制 涉及知识点: 路由守卫 Vuex使用 Vue自定义指令 导航守卫 前端工程采用Github开源项目Vue-element-admin作为模板,该项目地址:Github | Vue-element-admin. 在Vue-element-admin模板项目的src/permission.js文件中,给出了路由守卫.加载动态路由的实现方案,在实现了基于不同角色加载动态路由的功能.我们只需要稍作改动,就能将基于角色加

  • vue自定义指令限制输入框输入值的步骤与完整代码

    需求 前端开发过程中,经常遇到表单校验的需求,比如校验用户输入框的内容,限制用户只能输入数字. 本文内容基于 element-ui,el-form 组件可以绑定 model.rule 用于表单内容校验,但如果有多个表单多个输入框那就得写很多个 rule,虽然方法可以通用可是使用起来也是比较繁琐的,可通过自定义执行实现一次注册,多次使用. Vue 自定义指令 我们使用 el-input 作为表单的输入框 1. 先注册一个自定义指令 import Vue from 'vue'; Vue.direct

  • Vue自定义指令实现弹窗拖拽四边拉伸及对角线拉伸效果

    引言 近期公司vue前端项目需求:实现弹窗的拖拽,四边拉伸及对角线拉伸,以及弹窗边界处理.本人使用vue的自定义指令编写了drag.js文件分享给大家一起学习,以下代码是本人提取出来的示意demo,仅供参考.这是本人前端小白的第一篇技术分享,如有错误的地方,请大家批评指正! 页面布局 <template> <div class="parameter" v-dialogDrag > <div class="title">标题 <

  • vue自定义指令之面板拖拽的实现

    前言 在指令里获取的this并不是vue对象,vnode.context才是vue对象,一般来说,指令最好不要访问vue上的data,以追求解耦,但是可以通过指令传进来的值去访问method或ref之类的. vue指令 官方文档其实已经解释的蛮清楚了,这里挑几个重点的来讲. 1.arguments el: 当前的node对象,用于操作dom binding:模版解析之后的值 vNode: Vue 编译生成的虚拟节点,可以在上面获取vue对象 oldVnode: 使用当前指令上一次变化的node内

  • Vue自定义指令拖拽功能示例

    下面给大家分享vue自定义指令拖拽功能代码,具体代码如下所示: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>实例方法</title> <meta name="viewport" content="width=device-width, initial-scale=1

  • vue实现弹窗拖拽效果

    本文实例为大家分享了vue实现弹窗拖拽效果的具体代码,供大家参考,具体内容如下 前言 实现拖拽其实简单来说就分为三步 一.创建一个js文件 因为本身dialog窗口不具备移动拖拽能力,所以需要以下方法去实现,在src/components同级文件下创建utils文件夹,然后创建名为directives.js的文件. directives.js的代码如下: import Vue from 'vue'   // v-dialogDrag: 弹窗拖拽 Vue.directive('dialogDrag

  • Vue自定义指令directive的使用方法分享

    1. 一个指令定义对象可以提供如下几个钩子函数(均为可选) bind:只调用一次,指令第一次绑定到元素时调用.在这里可以进行一次性的初始化设置. inserted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中). update:只要当前元素不被移除,其他操作几乎都会触发这2个生命周期,先触发update后触发componentUpdate.虚拟DOM什么时候更新:只要涉及到元素的隐藏.显示(display)值的改变.内容的改变等都会触发虚拟DOM更新. component

  • vue自定义指令用法经典实例小结

    本文实例总结了vue自定义指令用法.分享给大家供大家参考,具体如下: 自定义指令: 一.属性: Vue.directive(指令名称,function(参数){ this.el -> 原生DOM元素 }); <div v-red="参数"></div> 指令名称:     v-red  ->  red * 注意: 必须以 v-开头 拖拽: 二.自定义元素指令:(用处不大) Vue.elementDirective('zns-red',{ bind:f

  • 8个非常实用的Vue自定义指令

    本文在github做了收录 github.com/Michael-lzg- demo源码地址 github.com/Michael-lzg- 在 Vue,除了核心功能默认内置的指令 ( v-model 和 v-show ),Vue 也允许注册自定义指令.它的作用价值在于当开发人员在某些场景下需要对普通 DOM 元素进行操作. Vue 自定义指令有全局注册和局部注册两种方式.先来看看注册全局指令的方式,通过 Vue.directive( id, [definition] ) 方式注册全局指令.然后

  • 如何使用vue自定义指令构建拖放插件

    我们都知道html5的拖放特性,利用它可以很方便的实现拖拽和放置功能,比如一些选择类操作的使用场景,让用户去拖拽比鼠标点击更容易接受和理解.今天我们就利用这一特性,结合vue的自定义指令,来实现一个简单但是实用的拖放插件. 为什么叫它插件?因为我们的目标不是开发一个vue组件,而是两个vue的自定义指令,并且最终会把这两个自定义指令封装到一个es6的class里,在实际项目中引入就可以很方便的使用了. 大部分的拖放使用场景都是把一些待选元素从A区域拖放到B区域.这里就涉及到两个概念,一个是可拖拽

  • vue实现element-ui对话框可拖拽功能

    element-ui对话框可拖拽及边界处理 应业务需求,需要实现对话框可拖拽问题,应element-ui没有提供官方支持,于是便参考大神的文章,得出了适合业务需要的解决方案.很多大神给出的代码是没有解决边界问题的,但是不解决边界问题存在一个bug,拖到不可视区域后边再也拖不回来了,不信你们可以试试. 在实现的功能的情况下,封装成了js文件,然后再main.js中引入后可全局使用. 还是上代码吧 功能实现代码directives.js代码如下: import Vue from 'vue'; //

  • vue移动端写的拖拽功能示例代码

    相关知识点 touchstart 当在屏幕上按下手指时触发 touchmove 当在屏幕上移动手指时触发 touchend 当在屏幕上抬起手指时触发 mousedown mousemove mouseup对应的是PC端的事件 touchcancel 当一些更高级别的事件发生的时候(如电话接入或者弹出信息)会取消当前的touch操作,即触发 touchcancel.一般会在touchcancel时暂停游戏.存档等操作. 效果图 实现步骤html 总结了一下评论,好像发现大家都碰到了滑动的问题.就在

随机推荐