likes
comments
collection
share

面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

作者站长头像
站长
· 阅读数 12

面试不用慌,写给初中级前端的高级进阶,读源码,多动手,合理利用摸鱼时间,提高自己的编码水平,本期带给大家Vue3 + ts 手写实现拖拽hook,收藏关注加点赞,是我更新的动力!

先看成果

面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

拖拽

拖拽的实现是比较常见的功能,现在很多的插件都能够实现,为了更好的熟练vue3和ts,笔者通过手写hooks的方式来对拖拽的功能进行实现。

页面UI部分

笔者需要实现一个hook,这个hook的功能是传入一个element和初始化坐标,实现对element的可拖拽,并且返回拖拽的实时坐标,下面的UI页面的代码

<template>
  <div ref="el" :style="style" style="position: fixed;border: 1px solid #409EFF;">
    我的坐标: {{ x }}, {{ y }}
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDraggable } from '../hooks/useDraggable'
const el = ref<HTMLElement | null>(null)

const { x, y, style } = useDraggable(el, {
  initPosition: { x: 40, y: 40 },
})
</script>

这里我们需要的是xy的坐标,所以需要我们的hook来进行返回

同时由于是拖拽,我们肯定是需要element元素的坐标进行移动的,我们先设置好position的定位,然后根据hook返回的left和top来实现拖拽

hook实现

1.处理传参和返回

我们接收一个dom元素和一个options的配置参数,由于我们可以通过ref的方式来传入dom或者是真实的dom,因为我们是通过ref来获取dom再传入的,所以这里的el需要用ts做一个处理,使用ref传入的类型和真实的dom的类型不同

这里可以看一下效果

ref获取的节点 面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

真实的节点 面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

下面我们用ts来对传入的参数做一个限制

2.参数ts处理

type MaybeRef<T> = T | Ref<T>

这里用到了泛型,MaybeRef的作用则是,处理传入的dom可能是HTMLElement类型也可能是ref<HTMLElement | null>类型

HTMLElement类型是真实的dom

ref<HTMLElement | null>类型是通过ref获取到的dom

然后对传入的配置做一个限制,这里先简单的进行定义dragOptions

type MaybeRef<T> = T | Ref<T>
interface dragOptions {
    initPosition?: Position
}
export function useDraggable(el: MaybeRef<HTMLElement | undefined | null>, options: dragOptions = {}) {
}

3.监听拖拽事件实现

通过getBoundingClientRect我们可以获取到元素的这些信息,如图

面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

3.1ts加强

由于传入的el元素有多种类型,如果是ref,那么则需要用到el.value来获取dom,但是笔者在写的时候忽略了另外一张el的类型根本就不存在value的值,所以这个地方出现了提示,这也恰恰说明了ts在编码过程中的好处,可以提前发现隐藏的问题,及时解决。

面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

解决方案

既然参数el可能是Htmlelement或者是ref 那么就利用ts来进行约束判断

这里利用泛型来进行书写,如果是ref,则返回el.value

function toValue<T>(el:MaybeRef<T>):T{
    if(isRef(el)){
        return el.value
    }
    return el
}

3.2 拖拽监听实现

isDragging来判断拖拽的开始和结束

tempPosition来保存拖拽的距离

position保存初始xy位置和拖动的xy位置

具体的计算这里就不详细说了,原理比较简单,通过代码可得

import { useEventListener } from '@vueuse/core'
interface Position {
    x: number
    y: number
}
let isDragging = false; // 是否正在拖动
const tempPosition = ref<Position>(
    { x: 0, y: 0 }
)
const position = reactive<Position>(
    { x: 0, y: 0 }
)
if (options.initPosition) {
    position.x = options.initPosition.x
    position.y = options.initPosition.y
}
const start = (e: PointerEvent) => {
    isDragging = true
    let { left, top } = toValue(el)!.getBoundingClientRect()
    tempPosition.value.x = e.clientX - left
    tempPosition.value.y = e.clientY - top
}
const move = (e: PointerEvent) => {
    if (!isDragging)
        return
    position.x = e.clientX - tempPosition.value.x
    position.y = e.clientY - tempPosition.value.y
}
const end = (e: PointerEvent) => {
    isDragging = false
    tempPosition.value.x = 0
    tempPosition.value.y = 0
}
useEventListener(el, 'pointerdown', start,true) 
useEventListener(window, 'pointermove', move,true)
useEventListener(el, 'pointerup', end,true)

3.3 注意move的监听方式

如果move事件加到整个el上,监听事件跟不上鼠标的移动,从而鼠标移出监听的元素,效果如下

面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

对此我们需要进行一些修改,把move的监听事件放到整个window上,这样的拖动效果会更加顺滑

处理返回

页面上我们需要用到的有两点,一个是xy的具体位置,另外一个是元素的偏移

元素的偏移我们用到computed来计算style进行返回

    return {
        ..._position,
        style: computed(
            () => `left:${position.x}px;top:${position.y}px;cursor:move;`,
        ),
    }

对于position,我想通过解构的方式传出,但这里需要解决一些问题

如果单纯的把position解构出来,那么可以看到x和y都不是响应式的,所以不能这么用 面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

let _position = toRefs(position)
    return {
        // ...position,
        ..._position,
        style: computed(
            // () => console.log(position.x)
            () => `left:${position.x}px;top:${position.y}px;cursor:move;`,
        ),
    }

需要用到toRefs把我们的Postion里面的值也转成响应式,然后再进行解构

面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

这样xy才能在页面上实现响应式。

代码完善

浏览器默认事件处理

按照下面的代码 每次拖拽的时候都会由于触发了冒泡行为而进行打印

<div @click="console.log('冒泡')" :style="style1" style="position: fixed;border: 1px solid #409EFF;height: 100px;width: 200px;" >
    <div style="color:#409EFF;" ref="el1" >👋 点我</div>
    <div style="color:red;margin-top: 20px;">此处无法拖拽</div>
  </div>

面试必备,学会使用 Vue3 + ts 手写实现拖拽hook

同时可能存在图片等默认的拖拽行为,我们也可以给防止,这里声明一个handleEvent

const handleEvent = (e: PointerEvent) => {
    e.preventDefault()
    e.stopPropagation()
}
const start = (e: PointerEvent) => {
    handleEvent(e)
}
const move = (e: PointerEvent) => {
    handleEvent(e)
}
const end = (e: PointerEvent) => {
    handleEvent(e)
}

"🙏 感谢您花时间阅读这篇文章!如果觉得有趣或有收获,请关注我的更新,给个喜欢和分享。您的支持是我写作的最大动力!✍️🌟"

往期好文推荐