vue自定义指令实现元素滑动移动端适配及边界处理

目录
  • 效果演示
  • 核心属性
  • 实现思路
  • 代码
  • 注意
    • 自定义指令this指向问题
    • 滑动后点击事件被触发
    • 移动端滑动问题

效果演示

核心属性

Element.clientWidth:元素可视宽度。

Element.clientHeight:元素可视高度。

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

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

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

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

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

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

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

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

实现思路

待滑动元素必须设置

position: fixed or absolute

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

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

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

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

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

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

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

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

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

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

代码

<template>
	<div class="ball-wrap" v-drag @touchmove.prevent>
	  <!-- 省略... -->
	</div>
</template>
<script>
export default {
  data() {
    return {
      isDrag: false
  },
  methods: {
    click() {
      if (this.isDrag) {
        return
      }
      // 省略...
    }
  },
  directives: {
    drag(el, binding, vnode) {
      /**
       * 获取客户端可见内容的高度
       *
       * @returns {number}
       */
      const getClientHeight = () => {
        return window.innerHeight || Math.min(document.documentElement.clientHeight, document.body.clientHeight)
      }
      /**
       * 获取客户端可见内容的宽度
       *
       * @returns {number}
       */
      const getClientWidth = () => {
        return window.innerWidth || Math.min(document.documentElement.clientWidth, document.body.clientWidth)
      }
      /**
       * startX = null:获取鼠标相对于元素(左上顶点)的x轴坐标(移动前坐标)
       * startX != null:获取移动后的左上顶点x轴坐标
       *
       * e.clientX:鼠标相对客户端(客户端左上顶点)的x轴坐标
       * el.offsetLeft:元素顶点(左上顶点)相对客户端(客户端左上顶点)的x轴坐标(元素必须脱离文档流,position: fixed or absolute)
       * el.clientWidth:元素宽度
       *
       * @param el
       * @param e
       * @param startX
       * @returns {number}
       */
      const getX = (el, e, startX) => {
        if (startX === null) {
          // 返回鼠标相对于元素(左上顶点)的x轴坐标
          return e.clientX - el.offsetLeft
        }
        // 客户端可视宽度
        const clientWidth = getClientWidth()
        // 元素自身宽度
        const elWidth = el.clientWidth
        // 移动到x轴位置
        let x = e.clientX - startX
        // 水平方向边界处理
        if (x <= 0) {
          // x轴最小为0
          x = 0
        } else if (x + elWidth > clientWidth) {
          // x是左上顶点的坐标,是否触碰到右边边界(超出可视宽度)要通过右顶点判断,所以需要加上元素自身宽度
          x = clientWidth - elWidth
        }
        return x
      }
      /**
       * startY = null:获取鼠标相对于元素(左上顶点)的y轴坐标(移动前坐标)
       * startY != null:获取移动后的左上顶点y轴坐标
       *
       * e.clientY:鼠标相对客户端(客户端左上顶点)的y轴坐标
       * el.offsetTop:元素顶点(左上顶点)相对客户端(客户端左上顶点)的y轴坐标(元素必须脱离文档流,position: fixed or absolute)
       * el.clientHeight:元素高度
       *
       * @param el
       * @param e
       * @param startY
       * @returns {number}
       */
      const getY = (el, e, startY) => {
        if (startY === null) {
          // 返回鼠标相对于元素(左上顶点)的y轴坐标
          return e.clientY - el.offsetTop
        }
        // 客户端可视高度
        const clientHeight = getClientHeight()
        // 元素自身高度
        const elHeight = el.clientHeight
        // 移动到y轴位置
        let y = e.clientY - startY
        // 垂直方向边界处理
        if (y <= 0) {
          // y轴最小为0
          y = 0
        } else if (y + elHeight > clientHeight) {
          // 同理,判断是否超出可视高度要加上自身高度
          y = clientHeight - elHeight
        }
        return y
      }
      /**
       * 监听鼠标按下事件(PC端拖动)
       *
       * @param e
       */
      el.onmousedown = (e) => {
        vnode.context.isDrag = false
        // 获取当前位置信息 (startX,startY)
        const startX = getX(el, e, null)
        const startY = getY(el, e, null)
        /**
         * 监听鼠标移动事件
         *
         * @param e
         */
        document.onmousemove = (e) => {
          // 标记正在移动,解决元素移动后点击事件被触发的问题
          vnode.context.isDrag = true
          // 更新元素位置(移动元素)
          el.style.left = getX(el, e, startX) + 'px'
          el.style.top = getY(el, e, startY) + 'px'
        }
        /**
         * 监听鼠标松开事件
         */
        document.onmouseup = () => {
          // 移除鼠标相关事件,防止元素无法脱离鼠标
          document.onmousemove = document.onmouseup = null
        }
      }
      /**
       * 监听手指按下事件(移动端拖动)
       * @param e
       */
      el.ontouchstart = (e) => {
        // 获取被触摸的元素
        const touch = e.targetTouches[0]
        // 获取当前位置信息 (startX,startY)
        const startX = getX(el, touch, null)
        const startY = getY(el, touch, null)
        /**
         * 监听手指移动事件
         * @param e
         */
        document.ontouchmove = (e) => {
          // 获取被触摸的元素
          const touch = e.targetTouches[0]
          // 更新元素位置(移动元素)
          el.style.left = getX(el, touch, startX) + 'px'
          el.style.top = getY(el, touch, startY) + 'px'
        }
        /**
         * 监听手指移开事件
         */
        document.ontouchend = () => {
          // 移除touch相关事件,防止元素无法脱离手指
          document.ontouchmove = document.ontouchend = null
        }
      }
    }
  }
}
</script>
<style scoped>
    .ball-wrap {
	position: fixed;
    }
</style>

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

注意

自定义指令this指向问题

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

滑动后点击事件被触发

鼠标事件触发顺序:

mouseover - mousedown - mouseup - click - mouseout

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

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

// ...
data()
  return {
    isDrag: false
  }
}
// ...
el.onmousedown = (e) => {
	// ...
	vnode.context.isDrag = false
	document.onmousemove = (e) => {
    	// 标记正在移动,解决元素移动后点击事件被触发的问题
       	vnode.context.isDrag = true
       	// ...
    }
}
// ...
methods: {
	click() {
	  if (this.isDrag) {
	    return
	  }
	  // ...
	}
}

移动端滑动问题

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

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

源码

github.com/anlingyi/xe…

以上就是vue自定义指令实现元素滑动移动端适配及边界处理的详细内容,更多关于vue元素滑动移动端适配指令的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue自定义指令v-focus实例详解

    目录 前言 自定义指令 directive 项目实际使用 技术背景 实际操作 优势 setTimeout(fn, 0) 永远的神 $nextTick(callback) 小结 前言 本文直接参考vue2.0官方文档, 并演示博主项目中的使用 自定义指令 directive 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令.注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件.然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就

  • vue3使用自定义指令实现el dialog拖拽功能示例详解

    目录 实现el-dialog的拖拽功能 通过自定义指令实现拖拽功能 实现拖拽功能 使用方式 实现el-dialog的拖拽功能 这里指的是 element-plus 的el-dialog组件,一开始该组件并没有实现拖拽的功能,当然现在可以通过设置属性的方式实现拖拽. 自带的拖拽功能非常严谨,拖拽时判断是否拖拽出窗口,如果出去了会阻止拖拽. 如果自带的拖拽功能可以满足需求的话,可以跳过本文. 通过自定义指令实现拖拽功能 因为要自己操作dom(设置事件),所以感觉还是使用自定义指令更直接一些,而且对原

  • VUE中的自定义指令钩子函数讲解

    目录 自定义指令钩子函数 自定义指令 先上官方解释 小贴士 钩子函数运行顺序 自定义指令钩子函数 自定义指令 除了VUE 内置指令外,VUE也支持我们自定义注册指令,分为局部和全局注册 但这些想必大家都不陌生,其中官方API也是写的明明白白 官方API点这里 而且自定义指令也会极大程度上帮助我们日常的编程,但这是很有意思的事情出现了,就是钩子函数,很多老铁都弄不明白这五个函数的具体区别 先上官方解释 bind:只调用一次,指令第一次绑定到元素时调用.在这里可以进行一次性的初始化设置. inser

  • vue中的for循环以及自定义指令解读

    目录 vue for循环及自定义指令 v-for 自定义指令 vue自定义指令动态参数 通过自定义指令中的修饰符的key作为值,更改显示的颜色 vue for循环及自定义指令 v-for 1.v-for用来循环的数组怎么发生变化可以被vue检测到: push.pop.shift.unshift.splice.sort.reverse等方法可以被检测到 vue对于这些方法的处理是重写了这些方法,并在最后会触发一次notify方法来通知这个array已经发生变化 vue还增加了两个方法来观测arra

  • Vue自定义指令中无法获取this的问题及解决

    目录 自定义指令中无法获取this 解决方法 Vue使用this的这几个坑你都知道吗 一.普通函数 二.Vue中的this 自定义指令中无法获取this 问题 最近在使用自定义指令时遇到一个问题,我想在指令里通过this直接去访问vue实例数据,但是显示未定义,经大佬提醒,里面的this很可能不是指向vue实例 解决方法 在函数里增加第三个参数vnode,vnode.context就是指向当前的vue实例 总结 指令里的this不是指向vue实例,可以使用vnode.context获取this

  • Vue自定义指令的使用详细介绍

    目录 1. 概述 2. 钩子函数 3. 自定义全局指令 4. 自定义局部指令 5. 使用自定义指令实现权限管理 6. 使用自定义指令实现表单验证 1. 概述 除了核心功能默认内置的指令,Vue也允许注册自定义指令.有的情况下,对普通 DOM 元素进行底层操作,这时候就会用到自定义指令绑定到元素上执行相关操作. 自定义指令分为: 全局指令和局部指令,当全局指令和局部指令同名时以局部指令为准. 局部指令:只对当前实例(或组件)生效 全局指令:对全部实例(或组件)都生效 2. 钩子函数 自定义指令常用

  • vue自定义指令实现元素滑动移动端适配及边界处理

    目录 效果演示 核心属性 实现思路 代码 注意 自定义指令this指向问题 滑动后点击事件被触发 移动端滑动问题 效果演示 核心属性 Element.clientWidth:元素可视宽度. Element.clientHeight:元素可视高度. MouseEvent.clientX:鼠标相对于浏览器左上顶点的水平坐标. MouseEvent.clientY:鼠标相对于浏览器左上顶点的垂直坐标. Touch.clientX:触点相对于浏览器左上顶点的水平坐标(移动端属性). Touch.clie

  • 关于Vue 自定义指令实现元素拖动的详细代码

    昨天在做的一个功能时,同时弹出多个框展示多个表格数据. 这些弹出框可以自由拖动.单独的拖动好实现,给元素绑定 mousedowm 事件. 这里就想到了 Vue 里面自定义指令来实现. 一.自定义指令 在使用自定义指令之前,先对自定义指令有一定的了解.从以下几个方面着手: 1.自定义指令定义范围 全局注册和组件内注册(注册的范围根据实际业务需求来) // 注册一个全局指令,可以在任何组件使用 Vue.directive('focus',{ // 当被绑定的元素插入 DOM 时 inserted:

  • 实现一个Vue自定义指令懒加载的方法示例

    在项目中如果有大量的图片需要加载的时候,就可以考虑使用懒加载了,懒加载其实就是监听浏览器的滚动,当滚动到一定的范围的时候就将图片的真实路径赋给src,然后取消监听.实现的方法也比较简单,可以通过懒加载的插件实现,也可以手写,手写通过vue自定义指令来实现,一般情况自定义指令用的也不多,比较vue自带的就够用了,大型复杂的项目的可能用的多. 什么是图片懒加载 当我们向下滚动的时候图片资源才被请求到,这也就是我们本次要实现的效果,进入页面的时候,只请求可视区域的图片资源这也就是懒加载. 比如我们加载

  • 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自定义指令使用方法详解

    Vue自定义指令的使用,具体内容如下 1.自定义指令的语法 Vue自定义指令语法如下: Vue.directive(id, definition) 传入的两个参数,id是指指令ID,definition是指定义对象.其中,定义对象可以提供一些钩子函数 2.钩子函数 定义对象的钩子函数如下: 钩子函数的参数 el: 指令所绑定的元素,可以用来直接操作 DOM . binding: 一个对象,包含以下属性: *name: 指令名,不包括 v- 前缀. *value: 指令的绑定值, 例如: v-my

  • Vue自定义指令介绍(2)

    Vue指令 Vue的指令以v-开头,作用在HTML元素上,将指令绑定在元素上,给绑定的元素添加一些特殊行为. 例如: <h1 v-if="yes">Yes</h1> 其中,v-是Vue的标识,if是指令ID,yes是expression.yes是MVVM中的VM即ViewModel,当它的值发生变化,就会触发指令,改变View视图的显示. expression还可以使用内联的模式,任何依赖的属性发生变化时都会触发指令的执行.如: <h1 v-if=&quo

  • vue自定义指令directive实例详解

    下面给大家介绍vue自定义指令directive,具体内容如下所示: 官网截图实例 vue除了一些核心的内部定义的指令(v-model,v-if,v-for,v-show)外,vue也允许用户注册自己的一些功能性的指令,有时候你实在是要对Dom操作,这个时候是自定义指令最合适的了. 来直接看例子:当页面加载时使得元素获得焦点(autofocus 在移动版 Safari 是不支持的),就是当页面加载好了,不做任何的操作使得表单自动获得焦点,光标自动在某个表单上代码如下: Vue.directive

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

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

  • Vue自定义指令写法与个人理解

    什么是Vue指令? 指令是一种可以附加到DOM元素的微命令(tiny commands). 它们通常以"v-"作为前缀, 以方便Vue知道你在使用一种特殊的标记, 从而确保语法的一致性. 如果你需要对HTML元素的低级别(low-level)访问来控制一些行为, 它们通常很有用. 如果你正在使用Vue(或者Angular), 你可能已经很熟悉其中的一些指令, 如: v-if, v-else等等. 我们将从了解一些基础开始, 但是如果你更愿意直接看例子, 请直接往下滚动. 这些例子也能很

  • 使用Vue自定义指令实现Select组件

    本篇文章教大家写一个非常简单的Select组件,想必很多人都写过Select,毕竟它太常用了,但是本篇文章的示例使用到了Vue的自定义指令,如果你对Vue自定义指令不怎么熟悉的话,本篇文章或许会让您有所收获! 完成的效果图如下: 一.首先,我们简单布局一下: <template> <div class="select"> <div class="inner"> <div class="inputWrapper&qu

随机推荐