v-drag

简介

v-drag 指令用于处理根据鼠标拖拽来变换目标元素形态、位置等的场景。

示例

拖动元素。

<template>
<article>
  <div
    ref="target"
    v-drag:target.translate
    class="target"
  />
</article>
</template>

<script>
import drag from 'veui/directives/drag'

export default {
  directives: {
    drag
  }
}
</script>

在指定元素区域内拖动。

<template>
<article>
  <div
    ref="container"
    class="container"
  >
    <div
      ref="target"
      v-drag:target.translate="{containment: 'container'}"
      class="target"
    />
  </div>
</article>
</template>

<script>
import drag from 'veui/directives/drag'

export default {
  directives: {
    drag
  }
}
</script>

拖动多个元素。

主元素
<template>
<article>
  <div
    ref="target1"
    v-drag:target1,target2,target3.translate
    class="target"
  >
    主元素
  </div>
  <div
    ref="target2"
    class="target"
  />
  <div
    ref="target3"
    class="target"
  />
</article>
</template>

<script>
import drag from 'veui/directives/drag'

export default {
  directives: {
    drag
  }
}
</script>

限制拖动方向。

水平方向
垂直方向
<template>
<article>
  <div
    ref="target1"
    v-drag:target1.translate.x
    class="target"
  >
    水平方向
  </div>
  <div
    ref="target2"
    v-drag:target2.translate.y
    class="target"
  >
    垂直方向
  </div>
</article>
</template>

<script>
import drag from 'veui/directives/drag'

export default {
  directives: {
    drag
  }
}
</script>

水平排序。

Axis: X(v-drag.sort.x)

0. 须菩提
1. 菩萨亦如是
2. 若作是言
3. 我当灭度无量众生
4. 即不名菩萨
5. 🍎🍎
6. 🍋
7. 🍉🍉🍉
8. 🍓🍓
9. 何以故
10. 须菩提
11. 无有法名为菩萨
12. 是故佛说
13. 一切法无我
14. 无人
15. 无众生
16. 无寿者
<template>
<article>
  <section ref="itemGroup">
    <h2>Axis: X(v-drag.sort.x)</h2>
    <transition-group
      ref="transitionGroup"
      name="list"
      tag="div"
      class="items"
    >
      <div
        v-for="item in items"
        :key="item"
        v-drag.sort.x="{
          name: 'mySortableButton',
          containment: 'itemGroup',
          callback: handleAxisXSortCallback,
          debug,
          align
        }"
        class="item"
      >
        {{ item }}
      </div>
    </transition-group>
  </section>
</article>
</template>

<script>
import drag from 'veui/directives/drag'

const items = [
  '须菩提',
  '菩萨亦如是',
  '若作是言',
  '我当灭度无量众生',
  '即不名菩萨',
  '🍎🍎',
  '🍋',
  '🍉🍉🍉',
  '🍓🍓',
  '何以故',
  '须菩提',
  '无有法名为菩萨',
  '是故佛说',
  '一切法无我',
  '无人',
  '无众生',
  '无寿者'
]

export default {
  directives: {
    drag
  },
  data () {
    return {
      debug: false,
      align: undefined,
      items: items.map((item, i) => `${i}. ${item}`)
    }
  },
  computed: {
    handleAxisXSortCallback () {
      return this.getTransitionSortCallback('items', 'transitionGroup')
    }
  },
  methods: {
    getTransitionSortCallback (itemsKey, transitionGroupRefKey) {
      return (toIndex, fromIndex) => {
        if (toIndex === fromIndex) {
          return
        }
        let promise
        if (transitionGroupRefKey) {
          promise = new Promise((resolve, reject) => {
            let el = this.$refs[transitionGroupRefKey].$el
            let handleTransitionEnd = () => {
              el.removeEventListener('transitionend', handleTransitionEnd)
              resolve()
            }
            el.addEventListener('transitionend', handleTransitionEnd)
          })
        }
        this.moveItem(this[itemsKey], fromIndex, toIndex)
        // 动画完了再回调成功
        return promise
      }
    },
    moveItem (items, fromIndex, toIndex) {
      let item = items[fromIndex]
      items.splice(fromIndex, 1)
      if (toIndex > fromIndex) {
        toIndex--
      }
      items.splice(toIndex, 0, item)
    }
  }
}
</script>

<style lang="less" scoped>
.list-move {
  // 动画曲线是 0.25, 0.1, 0.25, 1,就是 ease
  transition: transform 200ms ease;
}
</style>

垂直排序。

Axis: Y(v-drag.sort.y)

  1. 0须菩提
  2. 1若菩萨作是言
  3. 2我当庄严佛土
  4. 3是不名菩萨
  5. 4何以故
  6. 5🦁
  7. 6🙈🙉🙊
  8. 7🐷🐶
  9. 8如来说
  10. 9庄严佛土者
  11. 10即非庄严
  12. 11是名庄严
  13. 12须菩提
  14. 13若菩萨通达无我法者
<template>
<article>
  <section>
    <h2>Axis: Y(v-drag.sort.y)</h2>
    <transition-group
      ref="transitionGroup2"
      name="list"
      tag="ol"
      class="list"
    >
      <li
        v-for="item in items2"
        :key="item"
        v-drag.sort.y="{
          name: 'otherSortableButton',
          callback: handleAxisYSortCallback,
          debug,
          align
        }"
        class="item"
      >
        {{ item }}
      </li>
    </transition-group>
  </section>
</article>
</template>

<script>
import drag from 'veui/directives/drag'

const items = [
  '须菩提',
  '若菩萨作是言',
  '我当庄严佛土',
  '是不名菩萨',
  '何以故',
  '🦁',
  '🙈🙉🙊',
  '🐷🐶',
  '如来说',
  '庄严佛土者',
  '即非庄严',
  '是名庄严',
  '须菩提',
  '若菩萨通达无我法者'
]

export default {
  directives: {
    drag
  },
  data () {
    return {
      debug: false,
      align: undefined,
      items2: items.map((item, i) => `${i}${item}`)
    }
  },
  computed: {
    handleAxisYSortCallback () {
      return this.getTransitionSortCallback('items2', 'transitionGroup2')
    },
  },
  methods: {
    getTransitionSortCallback (itemsKey, transitionGroupRefKey) {
      return (toIndex, fromIndex) => {
        if (toIndex === fromIndex) {
          return
        }
        let promise
        if (transitionGroupRefKey) {
          promise = new Promise((resolve, reject) => {
            let el = this.$refs[transitionGroupRefKey].$el
            let handleTransitionEnd = () => {
              el.removeEventListener('transitionend', handleTransitionEnd)
              resolve()
            }
            el.addEventListener('transitionend', handleTransitionEnd)
          })
        }
        this.moveItem(this[itemsKey], fromIndex, toIndex)
        // 动画完了再回调成功
        return promise
      }
    },
    moveItem (items, fromIndex, toIndex) {
      let item = items[fromIndex]
      items.splice(fromIndex, 1)
      if (toIndex > fromIndex) {
        toIndex--
      }
      items.splice(toIndex, 0, item)
    }
  }
}
</script>

<style lang="less" scoped>
.list-move {
  // 动画曲线是 0.25, 0.1, 0.25, 1,就是 ease
  transition: transform 200ms ease;
}
</style>

API

指令的具体用法请参考官方文档。更多详细参数请参考自定义指令

绑定值

类型:Object

参数类型默认值描述
targetsArray<string|Vue|HTMLElement>[]

该参数指定了目标元素集合,在指令所在元素上拖拽鼠标的时候,会按照指定的方式变换所有目标元素。

类型描述
string在指令所在组件上下文中,根据 ref 查找指定的 DOM 元素集合。
Vue组件实例,直接使用 vm.$el 元素。
HTMLElementDOM 元素,直接使用。
typestring-该参数指定变化的类型,目前内置了 translate 类型(变换目标元素位置),可以进行扩展
draggablebooleantrue是否响应鼠标拖拽操作。
containmentstring|Vue|HTMLElement|Object-

目标元素在变换的时候,应当始终位于 containment 所指定的区域内。

如果通过 containment 解析出来是一个 DOM 元素,那么所有目标元素就应当在该元素内变换;如果解析出来是一个矩形区域描述(相对于视口的 top、left、width、height),那么所有目标元素就应当在该矩形区域内变换。

类型描述
string如果以 @ 开头(可以通过全局配置 drag.prefix 修改),就被认为是特殊逻辑,会透传给具体的 Handler 处理;否则,在指令所在组件上下文中,根据 ref 查找指定的 DOM 元素。
Vue根据组件实例找到 DOM 元素。
HTMLElement直接接收 DOM 元素。
Object接受任意包含 {top, left, width, height} 字段的普通对象,表示相对于视口的矩形区域的坐标和尺寸,四个字段均为 number 类型。
axisstring-限制所有目标元素只能在水平或者垂直方向上做变换。取值为:xy
dragstartfunction(): Objectfunction() {}

鼠标拖拽开始事件的回调函数。回调参数为 ({ event: DragEvent })

dragfunction(): Objectfunction() {}

鼠标拖拽中事件的回调函数。回调参数为 ({ event, distanceX, distanceY })

参数类型描述
eventDragEvent原生拖拽事件对象。
distanceXnumber水平方向自拖拽开始以后移动的总距离。
distanceYnumber垂直方向自拖拽开始以后移动的总距离。
disabledbooleanfalse该指令是否被禁用。
dragendfunction(): Objectfunction() {}鼠标拖拽结束事件的回调函数。回调参数同 drag
readyfunctionfunction() {}指令初始化完成的回调函数,会传出一个句柄对象参数,该对象上有一个 reset() 方法,用于将所有目标元素重置为变换之前的样子。

Object 类型提供的参数会覆盖通过指令参数、修饰符指定的参数。

修饰符

对应 Object 绑定值中的 type/axis。例如:

<!-- 沿着垂直方向做位移变换 -->
<div v-drag.translate.y></div>

参数

对应 Object 绑定值中的 targets。值是一个用 , 分隔的、表示一到多个 ref 的字符串。例如:

<div v-drag:box1,box2></div>

拖拽排序(v-drag.sort)

可以通过 v-drag.sortv-drag="{type: 'sort', ...}" 来实现拖拽排序。

绑定值

类型:Object

参数类型默认值描述
namestring-用来标记一组项目,在这组项目中进行排序。
typestring-该参数指定变化的类型,拖拽排序是 sort
containmentstring|Vue|HTMLElement|Object-参见上面的基础描述
axisstring-限制所有目标元素只能在水平或者垂直方向上做排序。取值为:xy
callbackfunction(toIndex: number, fromIndex: number): void-

排序指令仅仅通过该回调告诉用户排序的情况,即:从原来位置(fromIndex)移动到新位置(toIndex),用户自己应该在该回调中对数据源进行调整位置。

回调触发时机:当 fromIndex 元素的中心点拖动到热区(toIndex 元素尾部和 toIndex+1 元素首部之间的区域)中触发。

debugbooleanfalse在开发模式(process.env.NODE_ENV === 'development')下在 DOM 上显示热区标记以方便调试。

扩展

可以通过继承 BaseHandler 扩展 v-drag 指令:

import BaseHandler from 'veui/directives/drag/BaseHandler'
import { registerHandler } from 'veui/directives/drag'

class RotateHandler extends BaseHandler { }

registerHandler('rotate', RotateHandler)

然后通过 type 参数使用 RotateHandler

<div v-drag="{ type: 'rotate' }"></div>
<!-- 或者 -->
<div v-drag.rotate></div>

type 的名称不能与已有的修饰符冲突。

BaseHandler

BaseHandler 中各成员的说明如下:

成员名类型描述
optionsObject解析出来的参数组成的对象。
contextVue指令所在的组件。
isDraggingboolean是否处于拖拽过程中。
startfunction(Object)绑定值中的 dragstart 字段。
dragfunction(Object)绑定值中的 drag 字段。
endfunction(Object)绑定值中的 dragend 字段。
destroyfunction()指令与 DOM 元素解绑的时候调用。
setOptionsfunction(options)设置参数。
resetfunction()重置变换。