2.3 Elements -- useDraggable

2.3 Elements – useDraggable

https://vueuse.org/core/useDraggable/

作用

让指定html元素变成可以拖动的。

官方示例

<script setup lang="ts">
import { ref } from 'vue'
import { useDraggable } from '@vueuse/core'

const el = ref<HTMLElement | null>(null)

// style 是一个响应式的计算属性,包含left和top的值
const { x, y, style } = useDraggable(el, {
  initialValue: { x: 40, y: 40 },
})
</script>

<template>
  <div ref="el" :style="style" style="position: fixed">
    Drag me! I am at {{x}}, {{y}}
  </div>
</template>

被移动的元素应该是可以修改位置的,比如上面例子中style="position: fixed"

  • 无渲染组件的用法
<UseDraggable :initialValue="{ x: 10, y: 10 }" v-slot="{ x, y }">
  Drag me! I am at {{x}}, {{y}}
</UseDraggable>

可以将额外的属性storageKeystorageType传递给组件,来启用元素位置的持久化。这样再次打开窗口,元素还是上次移动到的位置。

<UseDraggable storage-key="vueuse-draggable" storage-type="session">
  Refresh the page and I am still in the same position!
</UseDraggable>

源码分析

源码地址:https://github.com/vueuse/vueuse/blob/main/packages/core/useDraggable/index.ts

1、定义初始变量

// 拖拽事件的监听目标,默认为全局window对象。超出这个目标,拖拽事件不可监控。
const draggingElement = options.draggingElement ?? defaultWindow
// 可以触发移动的区域,默认是目标元素
const draggingHandle = options.handle ?? target
// 初始位置,默认0,0
const position = ref<Position>(resolveUnref(options.initialValue) ?? { x: 0, y: 0 })
// 初始位置和当前位置的差值
const pressedDelta = ref<Position>()

2、注册监听事件

// 以默认值来解释
// 监听target的pointerdown事件,触发start事件,在捕获阶段处理。
useEventListener(draggingHandle, 'pointerdown', start, true)
// 监听window的pointermove事件,触发move事件,在捕获阶段处理。
useEventListener(draggingElement, 'pointermove', move, true)
useEventListener(draggingElement, 'pointerup', end, true)

⚠️:pointerdown、pointermove、pointerup这几个变量是为了同时适用于webh5,相当于在web触发mouse事件,在h5触发touch事件。但是h5需要根据浏览器版本来看,低版本可能不兼容,需要改成touchmove等变量。

https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event

3、分别看三个方法都做了什么

3.1、start事件,主要功能如下:

  1. 判断是否需要出发start事件
  2. 记录鼠标点和target左上角的距离pressedDelta
const start = (e: PointerEvent) => {
  // 不是['mouse', 'touch', 'pen']事件,不处理
  if (!filterEvent(e))
    return
  
  // exact表示精确:如果点击的元素和target不是同一个元素,不处理,比如子元素
  if (resolveUnref(options.exact) && e.target !== resolveUnref(target))
    return
  // getBoundingClientRect:获取target相对于视口的位置
  const rect = resolveUnref(target)!.getBoundingClientRect()
  // 获取鼠标点和target的距离
  const pos = {
    x: e.clientX - rect.left,
    y: e.clientY - rect.top,
  }
  if (options.onStart?.(pos, e) === false)
    return
  // 记下这个距离,以便在move和end函数中使用
  pressedDelta.value = pos
  // 处理一些默认事件
  handleEvent(e)
}

const handleEvent = (e: PointerEvent) => {
  if (resolveUnref(options.preventDefault))
    e.preventDefault()
  if (resolveUnref(options.stopPropagation))
    e.stopPropagation()
}

在这里插入图片描述

3.2、move事件

随着鼠标移动,onMove事件触发,不断计算 position.value ,计算的方式是鼠标位置和pressedDelta计算差值。

从图上来看,需要算的是蓝线的长度,它的值就等于绿线的长度剪去黄线的长度。

const move = (e: PointerEvent) => {
  if (!filterEvent(e))
    return
  if (!pressedDelta.value)
    return
  
  // 主要就是这一步
  position.value = {
    x: e.clientX - pressedDelta.value.x,
    y: e.clientY - pressedDelta.value.y,
  }
  options.onMove?.(position.value, e)
  handleEvent(e)
}


/**
* 通过position这个变量,响应式修改style的值,最后返回的style是一个计算属性
*/
return {
  style: computed(() => `left:${position.value.x}px;top:${position.value.y}px;`),
}

在这里插入图片描述

3.3、end事件,主要功能是清除pressedDelta的值

const end = (e: PointerEvent) => {
  if (!filterEvent(e))
    return
  if (!pressedDelta.value)
    return
  
  // 清除pressedDelta的值,下次点击的时候,重新记录新的
  pressedDelta.value = undefined
  options.onEnd?.(position.value, e)
  handleEvent(e)
}

从代码来看,这个函数不处理滚动,只监听视口内的拖动事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值