Skip to content

vue3 手势插件

useDrag 拖拽 - 基础使用

useDrag 拖拽连接两个点
<template>
  <div class="flex fill center gesture-scroll">
      <svg class="svg" viewBox="-14.5 -14.5 328 28" version="1.1" xmlns="http://www.w3.org/2000/svg">
        <circle class="from" v-bind="bind()" fill="hotpink" cx="0" cy="0" r="12" />
        <line x1="0" y1="0" :x2="state.x2" :y2="state.y2" stroke="hotpink" strokeLinecap="square" strokeWidth="2" />
        <circle ref="targetRef" class="target" cx="300" cy="0" r="12" :fill="tColor" />
      </svg>
      <div class="status">{{text}} </div>
    </div>
</template>

<script setup>
import { watch, ref, computed } from 'vue'
import { useDrag } from '@use-gesture-x/vue3'
const targetRef = ref(null)
const attached = ref(false)
const dragging = ref(false)
const state = ref({ x2: 0, y2: 0 })
const tColor = computed(() => attached.value ? 'hotpink' : 'blue')
const text = computed(() => attached.value ? dragging.value ? '你可以松开指针' : '点被连接起来了!'
  : '连接粉色点和蓝色点')

const bind = useDrag(({ xy: [x, y], active, last, movement: [mx, my] }) => {
  dragging.value = active
  attached.value = document.elementFromPoint(x, y) === targetRef.value
  if (last) {
    state.value = { x2: attached.value ? 300 : 0, y2: 0 }
  } else {
    state.value = { x2: mx, y2: my }
  }
})
</script>
<style scoped>
html,
body,
#root {
  height: 100%;
  width: 100%;
}

body {
  font-family: system-ui, sans-serif;
  min-height: 100vh;
  margin: 0;
}

*,
*:after,
*:before {
  box-sizing: border-box;
}

.flex {
  display: flex;
  align-items: center;
}

.flex.fill {
  height: 100%;
}

.flex.center {
  justify-content: center;
}

.svg {
  position: relative;
  min-width: 328px;
  max-width: 328px;
  overflow: visible;
  touch-action: none;
}

.from {
  cursor: pointer;
  touch-action: none;
}

.status {
  position: absolute;
  font-size: 0.8em;
}

</style>

useDrag 拖拽 - 拖拽排序

useDrag拖拽排序
<template>
  <div class="flex fill center container">
    <div class="content">
      <template v-for="(spring, i) in springs" :key="i">
        <div
          v-bind="bind(i)"
          :style="getStyle(spring)"
        >
        {{items[i]}}
        </div>
    </template>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { watch, reactive, ref, computed } from 'vue'
import { useDrag } from '@use-gesture-x/vue3'
import { useSpring } from './useSpring'
import clamp from 'lodash.clamp'
import swap from 'lodash-move'

const fn =
  (order: number[], active = false, originalIndex = 0, curIndex = 0, y = 0) =>
  (index: number) => active && index === originalIndex
    ? { y: curIndex * 100 + y, scale: 1.1, zIndex: 1, shadow: 15 }
      : { y: order.indexOf(index) * 100, scale: 1, zIndex: 0, shadow: 1 }
// 解释:产生多个元素的动画
const useSprings = (order: number[]) => order.map((_, i) => useSpring(fn(order)(i)))
const items = 'Lorem ipsum dolor sit'.split(' ')
const order = ref(items.map((_, i) => i))
const springs = useSprings(order.value)
const getStyle = (spring) => {
  return {
    ...spring[0].value,
    zIndex: spring[2].zIndex,
    boxShadow: `rgba(0, 0, 0, 0.15) 0px ${spring[2].shadow}px ${2 * spring[2].shadow}px 0px`,
  }
}
const bind = useDrag(({ args: [originalIndex], active, movement: [, y] }) => {
  const curIndex = order.value.indexOf(originalIndex)
  const curRow = clamp(Math.round((curIndex * 100 + y) / 100), 0, items.length - 1)
  const newOrder = swap(order.value, curIndex, curRow)
  springs.forEach((spring, i) => {
    const api = spring[1]
    api.set(fn(newOrder, active, originalIndex, curIndex, y)(i))
  })
  if (!active) order.value = newOrder
})
</script>
<style scoped>
html,
body,
#root {
  height: 100%;
  width: 100%;
}

body {
  font-family: system-ui, sans-serif;
  min-height: 100vh;
  margin: 0;
}

*,
*:after,
*:before {
  box-sizing: border-box;
}

.flex {
  display: flex;
  align-items: flex-start;
}

.flex.fill {
  height: 100%;
}

.flex.center {
  justify-content: center;
}

.container {
  width: 100%;
  height: 100%;
}

.content {
  padding-top: 24px;
  width: 320px;
}

.content > div {
  position: absolute;
  width: 320px;
  height: 80px;
  transform-origin: 50% 50% 0px;
  border-radius: 5px;
  color: white;
  line-height: 40px;
  padding-left: 32px;
  font-size: 14.5px;
  background: lightblue;
  text-transform: uppercase;
  letter-spacing: 2px;
  touch-action: none;
}

.content > div:nth-child(1) {
  background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
}
.content > div:nth-child(2) {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.content > div:nth-child(3) {
  background: linear-gradient(135deg, #5ee7df 0%, #b490ca 100%);
}
.content > div:nth-child(4) {
  background: linear-gradient(135deg, #c3cfe2 0%, #c3cfe2 100%);
}

</style>

useDrag 拖拽 - 调控设置面板案例

useDrag拖拽,调控设置面板案例
<template>
  <div class="flex fill center container">
    <ConfigPanel v-model="config" />
    <div v-bind="bind()" class="drag" :style="style">
      <div class="text">
        <span>拖拽观察位子:bind</span>
        <span> x:{{ Math.round(coords.x) }}, y:{{ Math.round(coords.y) }} </span>
      </div>
    </div>
    <div ref="boundRef" className="hover" />
  </div>
</template>

<script setup>
import { watch, reactive, ref, computed } from 'vue'
import ConfigPanel from './components/ConfigPanle.vue'
import { useDrag } from '@use-gesture-x/vue3'
import { useSpring } from './useSpring'
const boundRef = ref(null)
const dragRef = ref(null)
const coords = ref({ x: 0, y: 0 })
const [style, api] = useSpring()
const config = ref({
  axis: '',
  delay: 0,
  enabled: true,
  gesture: 'movement',
  rubberband: false,
  bounds: false
})
const dragConfig = computed(() => ({
  ...config.value,
  bounds: config.value.bounds ? boundRef : undefined
}))
const bind = useDrag(({ active, movement: [x, y] }) => {
  const changeInfo = {
    x: active ? x : 0,
    y: active ? y : 0,
    scale: active ? 1.2 : 1
  }
  api.set(changeInfo)
  coords.value = changeInfo
}, dragConfig)
</script>
<style scoped>
html,
body,
#root {
  height: 100%;
  width: 100%;
}

body {
  font-family: system-ui, sans-serif;
  min-height: 100vh;
  margin: 0;
}

*,
*:after,
*:before {
  box-sizing: border-box;
}

.flex {
  display: flex;
  align-items: center;
}

.flex.fill {
  height: 100%;
}

.flex.center {
  justify-content: center;
}

.container {
  width: 100%;
  height: 100%;
}

.drag {
  position: absolute;
  height: 120px;
  width: 120px;
  background-color: #ec625c;
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  font-size: 10px;
}

.drag > div {
  margin: 10%;
  width: 80%;
  height: 80%;
  background-color: #000;
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-family: monospace;
}

.hover {
  height: 300px;
  width: 300px;
  background-color: royalblue;
  &::before {
    content: '可在右侧面板启用范围限制,蓝色为拖动范围区域。(1、元素范围 2、下左右坐标)';
    color: #fff;
  }
}

.hover:hover {
  background-color: darkblue;
}
</style>