Vue实战:利用自定义指令实现鼠标拖动元素效果

 3460

本篇文章分享一个Vue实战,介绍下使用Vue的自定义指令实现鼠标拖动元素的效果以及解决移动端适配的问题。


Vue实战:利用自定义指令实现鼠标拖动元素效果


核心属性

Element.clientWidth:元素可视宽度。

Element.clientHeight:元素可视高度。

MouseEvent.clientX:鼠标相对于浏览器左上顶点的水平坐标。

MouseEvent.clientY:鼠标相对于浏览器左上顶点的垂直坐标。

Touch.clientX:触点相对于浏览器左上顶点的水平坐标(移动端属性)。

Touch.clientY:触点相对于浏览器左上顶点的垂直坐标(移动端属性)。

HTMLElement.offsetLeft:当前元素左上角相对于父节点(HTMLElement.offsetParent)的左边偏移的距离。当元素脱离文档流时(position: fixed)则相对于原点(浏览器左上顶点)偏移。【相关推荐:vuejs视频教程】

HTMLElement.offsetTop:当前元素左上角相对于父节点(HTMLElement.offsetParent)的顶部偏移的距离。当元素脱离文档流时(position: fixed)则相对于原点(浏览器左上顶点)偏移。

Element.style.top:可读可写,值为 offsetTop

Element.style.left:可读可写,值为 offsetLeft


Vue实战:利用自定义指令实现鼠标拖动元素效果


实现思路

待滑动元素必须设置 position: fixed or absolute

元素滑动需要依赖于鼠标的移动,鼠标的移动位置决定了元素滑动的位置,元素的位置是通过调整左上顶点坐标来的,所以我们要知道元素滑动后的左上顶点坐标,这样才能将元素移动到指定位置(鼠标悬停的位置)。

首先要计算出鼠标在移动元素前相对元素的位置 (x, y) :

  1. // 鼠标当前的位置减去元素当前的位置
  2. (x, y) = (e.clientX - el.offsetLeft, e.clientY - el.offsetTop)

鼠标相对元素位置是指相对于元素左上顶点的位置。

e 指鼠标事件,el 指滑动的元素。

知道了鼠标的相对位置,后续的鼠标移动,只要知道移动后的鼠标坐标,就能很容易的把元素的左上顶点坐标算出来。

计算元素移动后的左上顶点坐标 (x', y') :

  1. // 鼠标当前的位置减去滑动前的相对位置
  2. (x', y') = (e.clientX - x, e.clientY - y)

(x', y') 就是要移动的最终坐标,然后调整元素位置即可

  1. el.style.left = x' + 'px'
  2. el.style.top = y' + 'px'


代码

  1. <template>
  2.     <div class="ball-wrap" v-drag @touchmove.prevent>
  3.       <!-- 省略... -->
  4.     </div>
  5. </template>
  6.  
  7. <script>
  8. export default {
  9.   data() {
  10.     return {
  11.       isDrag: false
  12.   },
  13.   methods: {
  14.     click() {
  15.       if (this.isDrag) {
  16.         return
  17.       }
  18.  
  19.       // 省略...
  20.     }
  21.   },
  22.   directives: {
  23.     drag(el, binding, vnode) {
  24.       /**
  25.        * 获取客户端可见内容的高度
  26.        *
  27.        * @returns {number}
  28.        */
  29.       const getClientHeight = () => {
  30.         return window.innerHeight || Math.min(document.documentElement.clientHeight, document.body.clientHeight)
  31.       }
  32.  
  33.       /**
  34.        * 获取客户端可见内容的宽度
  35.        *
  36.        * @returns {number}
  37.        */
  38.       const getClientWidth = () => {
  39.         return window.innerWidth || Math.min(document.documentElement.clientWidth, document.body.clientWidth)
  40.       }
  41.  
  42.       /**
  43.        * startX = null:获取鼠标相对于元素(左上顶点)的x轴坐标(移动前坐标)
  44.        * startX != null:获取移动后的左上顶点x轴坐标
  45.        *
  46.        * e.clientX:鼠标相对客户端(客户端左上顶点)的x轴坐标
  47.        * el.offsetLeft:元素顶点(左上顶点)相对客户端(客户端左上顶点)的x轴坐标(元素必须脱离文档流,position: fixed or absolute)
  48.        * el.clientWidth:元素宽度
  49.        *
  50.        * @param el
  51.        * @param e
  52.        * @param startX
  53.        * @returns {number}
  54.        */
  55.       const getX = (el, e, startX) => {
  56.         if (startX === null) {
  57.           // 返回鼠标相对于元素(左上顶点)的x轴坐标
  58.           return e.clientX - el.offsetLeft
  59.         }
  60.  
  61.         // 客户端可视宽度
  62.         const clientWidth = getClientWidth()
  63.         // 元素自身宽度
  64.         const elWidth = el.clientWidth
  65.  
  66.         // 移动到x轴位置
  67.         let x = e.clientX - startX
  68.         // 水平方向边界处理
  69.         if (<= 0) {
  70.           // x轴最小为0
  71.           x = 0
  72.         } else if (+ elWidth > clientWidth) {
  73.           // x是左上顶点的坐标,是否触碰到右边边界(超出可视宽度)要通过右顶点判断,所以需要加上元素自身宽度
  74.           x = clientWidth - elWidth
  75.         }
  76.  
  77.         return x
  78.       }
  79.  
  80.       /**
  81.        * startY = null:获取鼠标相对于元素(左上顶点)的y轴坐标(移动前坐标)
  82.        * startY != null:获取移动后的左上顶点y轴坐标
  83.        *
  84.        * e.clientY:鼠标相对客户端(客户端左上顶点)的y轴坐标
  85.        * el.offsetTop:元素顶点(左上顶点)相对客户端(客户端左上顶点)的y轴坐标(元素必须脱离文档流,position: fixed or absolute)
  86.        * el.clientHeight:元素高度
  87.        *
  88.        * @param el
  89.        * @param e
  90.        * @param startY
  91.        * @returns {number}
  92.        */
  93.       const getY = (el, e, startY) => {
  94.         if (startY === null) {
  95.           // 返回鼠标相对于元素(左上顶点)的y轴坐标
  96.           return e.clientY - el.offsetTop
  97.         }
  98.  
  99.         // 客户端可视高度
  100.         const clientHeight = getClientHeight()
  101.         // 元素自身高度
  102.         const elHeight = el.clientHeight
  103.  
  104.         // 移动到y轴位置
  105.         let y = e.clientY - startY
  106.         // 垂直方向边界处理
  107.         if (<= 0) {
  108.           // y轴最小为0
  109.           y = 0
  110.         } else if (+ elHeight > clientHeight) {
  111.           // 同理,判断是否超出可视高度要加上自身高度
  112.           y = clientHeight - elHeight
  113.         }
  114.  
  115.         return y
  116.       }
  117.  
  118.       /**
  119.        * 监听鼠标按下事件(PC端拖动)
  120.        *
  121.        * @param e
  122.        */
  123.       el.onmousedown = (e) => {
  124.         vnode.context.isDrag = false
  125.  
  126.         // 获取当前位置信息 (startX,startY)
  127.         const startX = getX(el, e, null)
  128.         const startY = getY(el, e, null)
  129.  
  130.         /**
  131.          * 监听鼠标移动事件
  132.          *
  133.          * @param e
  134.          */
  135.         document.onmousemove = (e) => {
  136.           // 标记正在移动,解决元素移动后点击事件被触发的问题
  137.           vnode.context.isDrag = true
  138.  
  139.           // 更新元素位置(移动元素)
  140.           el.style.left = getX(el, e, startX) + 'px'
  141.           el.style.top = getY(el, e, startY) + 'px'
  142.         }
  143.  
  144.         /**
  145.          * 监听鼠标松开事件
  146.          */
  147.         document.onmouseup = () => {
  148.           // 移除鼠标相关事件,防止元素无法脱离鼠标
  149.           document.onmousemove = document.onmouseup = null
  150.         }
  151.       }
  152.  
  153.       /**
  154.        * 监听手指按下事件(移动端拖动)
  155.        * @param e
  156.        */
  157.       el.ontouchstart = (e) => {
  158.         // 获取被触摸的元素
  159.         const touch = e.targetTouches[0]
  160.         // 获取当前位置信息 (startX,startY)
  161.         const startX = getX(el, touch, null)
  162.         const startY = getY(el, touch, null)
  163.  
  164.         /**
  165.          * 监听手指移动事件
  166.          * @param e
  167.          */
  168.         document.ontouchmove = (e) => {
  169.           // 获取被触摸的元素
  170.           const touch = e.targetTouches[0]
  171.           // 更新元素位置(移动元素)
  172.           el.style.left = getX(el, touch, startX) + 'px'
  173.           el.style.top = getY(el, touch, startY) + 'px'
  174.         }
  175.  
  176.         /**
  177.          * 监听手指移开事件
  178.          */
  179.         document.ontouchend = () => {
  180.           // 移除touch相关事件,防止元素无法脱离手指
  181.           document.ontouchmove = document.ontouchend = null
  182.         }
  183.       }
  184.     }
  185.   }
  186. }
  187. </script>
  188.  
  189. <style scoped>
  190.     .ball-wrap {
  191.     position: fixed;
  192.     }
  193. </style>

drag 是我们自定义的指令,在需要滑动的元素上绑定 v-drag 即可。


注意

自定义指令this指向问题

在自定义指令 directives 内不能访问 this,如果需要修改 data 里的值,需要通过 vnode.context.字段名 = 值 修改。


滑动后点击事件被触发

鼠标事件触发顺序:

mouseover - mousedown - mouseup - click - mouseout

滑动的前提是鼠标必须按下再滑动,所以在我们滑动完毕松开鼠标时,click 事件会被触发。

解决方法:定义一个标志变量,表示是否是滑动,点击事件执行时,将此变量作为前置条件,如果是在滑动则不执行

  1. // ...
  2.  
  3. data() 
  4.   return {
  5.     isDrag: false
  6.   }
  7. }
  8.  
  9. // ...
  10.  
  11. el.onmousedown = (e) => {
  12.     // ...
  13.     vnode.context.isDrag = false
  14.     document.onmousemove = (e) => {
  15.         // 标记正在移动,解决元素移动后点击事件被触发的问题
  16.         vnode.context.isDrag = true
  17.         // ...
  18.     }
  19. }
  20.  
  21. // ...
  22.  
  23. methods: {
  24.     click() {
  25.       if (this.isDrag) {
  26.         return
  27.       }
  28.  
  29.       // ...
  30.     }
  31. }


移动端滑动问题

移动端滑动时会触发默认事件,导致滑动卡顿。

在要触发滑动的元素上加上 @touchmove.prevent,以阻止默认事件的发生。


源码

https://github.com/anlingyi/xeblog-vue/blob/master/src/components/xe-pokeball/index.vue


TAG标签:
本文网址:https://www.zztuku.com/detail-13080.html
站长图库 - Vue实战:利用自定义指令实现鼠标拖动元素效果
申明:本文转载于《掘金社区》,如有侵犯,请 联系我们 删除。

评论(0)条

您还没有登录,请 登录 后发表评论!

提示:请勿发布广告垃圾评论,否则封号处理!!

    编辑推荐