最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解如何在Vue2中實(shí)現(xiàn)useDraggable

 更新時(shí)間:2023年12月06日 09:52:12   作者:何期驟雨降青霄  
這篇文章主要為大家詳細(xì)介紹了Vue2中實(shí)現(xiàn)useDraggable的相關(guān)知識(shí),文中的示例代碼簡(jiǎn)潔易懂,對(duì)我們深入了解vue有一定的幫助,需要的小伙伴可以參考下

前言

最近接到個(gè)需求:要使 Modal 組件可以被拖拽。接到需求后立馬想到使用 mousedown mousemove mouseup 等事件及定位去實(shí)現(xiàn),于是一頓操作后實(shí)現(xiàn)了一個(gè) useMovable hook。但總覺得不夠完美,有以下問題:

  • 被拖拽元素必須是定位元素,否則無法拖拽
  • 有一個(gè)極難復(fù)現(xiàn)的bug,在開發(fā)環(huán)境甚至沒有復(fù)現(xiàn)過,生產(chǎn)環(huán)境也極少復(fù)現(xiàn),因此一直未找到問題所在。

于是,就想到去看下一些開源組件庫是如何實(shí)現(xiàn)拖拽的,最終在 element-plus 中找到了(雖然 element-plus 是基于 Vue3 的,但在 Vue2.7 中同樣可以使用);那么我們就來看看它的源碼。

useDraggable 源碼解讀

import { onBeforeUnmount, onMounted, watchEffect } from 'vue'
import type { ComputedRef, Ref } from 'vue'

function toCssValue (val?: number | string | null): string {
  if (val == null) return ''
  if (typeof val === 'number') return `${val}px`
  return val
}

/**
 * 使目標(biāo)元素可以被拖動(dòng)的 hook
 * @param targetRef 目標(biāo)元素,即被拖動(dòng)的元素
 * @param dragRef 可執(zhí)行拖動(dòng)的元素
 */
export function useDraggable (
  targetRef: Ref<HTMLElement | null | undefined>,
  dragRef: Ref<HTMLElement | null | undefined>,
  draggable: ComputedRef<boolean>,
) {
  let transform = {
    offsetX: 0,
    offsetY: 0,
  }

  const onMousedown = (e: MouseEvent) => {
    const downX = e.clientX
    const downY = e.clientY
    const { offsetX, offsetY } = transform

    const targetRect = targetRef.value!.getBoundingClientRect()
    const targetLeft = targetRect.left
    const targetTop = targetRect.top
    const targetWidth = targetRect.width
    const targetHeight = targetRect.height

    const clientWidth = document.documentElement.clientWidth
    const clientHeight = document.documentElement.clientHeight

    const minLeft = -targetLeft + offsetX // translateX 最小值
    const minTop = -targetTop + offsetY // translateY 最小值
    const maxLeft = clientWidth - targetLeft - targetWidth + offsetX // translateX 最大值
    const maxTop = clientHeight - targetTop - targetHeight + offsetY // translateY 最大值

    const onMousemove = (e: MouseEvent) => {
      // 獲取移動(dòng)偏移量,同時(shí)保證在視口范圍內(nèi)
      const moveX = Math.min(
        Math.max(offsetX + e.clientX - downX, minLeft),
        maxLeft,
      )
      const moveY = Math.min(
        Math.max(offsetY + e.clientY - downY, minTop),
        maxTop,
      )

      transform = {
        offsetX: moveX,
        offsetY: moveY,
      }
      // 源碼中使用了 addUnit,這里我做了點(diǎn)小改動(dòng)
      targetRef.value!.style.transform = `translate(${toCssValue(moveX)}, ${toCssValue(moveY)})`
    }

    const onMouseup = () => {
      document.removeEventListener('mousemove', onMousemove)
      document.removeEventListener('mouseup', onMouseup)
    }

    document.addEventListener('mousemove', onMousemove)
    document.addEventListener('mouseup', onMouseup)
  }

  const onDraggable = () => {
    if (dragRef.value && targetRef.value) {
      dragRef.value.addEventListener('mousedown', onMousedown)
    }
  }

  const offDraggable = () => {
    if (dragRef.value && targetRef.value) {
      dragRef.value.removeEventListener('mousedown', onMousedown)
    }
  }

  onMounted(() => {
    watchEffect(() => {
      if (draggable.value) {
        onDraggable()
      } else {
        offDraggable()
      }
    })
  })

  onBeforeUnmount(() => {
    offDraggable()
  })
}

可以看到,這里的拖拽是通過 transform 實(shí)現(xiàn)的,這就解決了之前提到過的元素必須是定位元素的問題。

同時(shí)為了保證元素拖拽時(shí)不被拖到視口之外,這里通過視口的寬高、元素的寬高、元素的位置等來計(jì)算出元素的 translate 的最大和最小值。

 // 保證在視口范圍內(nèi)主要是以下代碼
const moveX = Math.min(
  Math.max(offsetX + e.clientX - downX, minLeft),
  maxLeft,
)
const moveY = Math.min(
  Math.max(offsetY + e.clientY - downY, minTop),
  maxTop,
)

另外,可以看到 useDraggable 接收了 targetRef dragRef 兩個(gè)參數(shù),分別表示被拖拽的元素和可以執(zhí)行拖拽的元素,這樣可以將兩個(gè)元素區(qū)分開了(當(dāng)然,是一個(gè)元素也完全沒有問題),便于實(shí)現(xiàn)如:在彈窗 header 部分按下鼠標(biāo)可以拖拽整個(gè)彈窗,而在彈窗 body / footer 部分按下則無法進(jìn)行拖拽的功能。

最后值得一提的是:draggable 參數(shù)的類型是 ComputedRef,這樣的好處就是可以監(jiān)聽 draggable 來動(dòng)態(tài)的綁定和解綁拖拽函數(shù)。

當(dāng)元素本身就具有 transform: translate 值時(shí)的處理方法

這樣似乎很完美,但測(cè)試過程中我發(fā)現(xiàn)一個(gè)問題:當(dāng)被拖拽元素本身就具有 transform: translate 值就會(huì)出現(xiàn)bug;原因是 transform 變量在初次拖拽時(shí)兩個(gè)屬性的值都是 0,而在保證元素必須在視口中的計(jì)算代碼中使用到了 transform 變量,而當(dāng)元素本身就具有 transform: translate 值時(shí)該計(jì)算就不再準(zhǔn)確。

解決這個(gè)問題的方法就是拿到元素初始的 transform: translate 值賦給 transform 變量,于是我寫下了如下代碼:

function getComputedStylePropertyValue (
  el: Element,
  property: string,
): string {
  const css = window.getComputedStyle(el, null)
  return css.getPropertyValue(property)
}

const cssTransform = getComputedStylePropertyValue(targetRef.value!, 'transform')

然后一打印 cssTransform 發(fā)現(xiàn)是一個(gè)字符串,類似這樣: matrix(1, 0, 0, 1, 10, 10),最后兩個(gè)數(shù)字代表 translateX 和 translateY 的值,但問題是如何取出來呢?

首先想到的是通過正則匹配取出再 parseFloat,但這樣顯然比較麻煩。于是我去搜索了一番,找到了 DOMMatrix,但它的兼容性較差,又經(jīng)過一番搜索找到了 WebKitCSSMatrix,于是就有以下代碼:

const setTransformInitialValue = () => {
  const Matrix = DOMMatrix || WebKitCSSMatrix
  const cssTransform = getComputedStylePropertyValue(targetRef.value!, 'transform')
  const matrix = new Matrix(cssTransform)

  transform = {
    offsetX: matrix.e || 0, // matrix.e 代表 translateX
    offsetY: matrix.f || 0, // matrix.f 代表 translateY
  }
}

只要把這個(gè)函數(shù)放在 onMousedown 函數(shù)體最上面調(diào)用一下就解決了這個(gè)問題。

結(jié)語

當(dāng)遇到問題時(shí)不妨多借鑒別人的代碼,尤其是第三方開源組件庫的源碼,也許你會(huì)有意想不到的收獲,思路一下子就打開了。但在借鑒別人代碼的同時(shí)你也得深入理解這段代碼,否則只是抄過來的話,需要新加需求時(shí)你可能就束手無策了。

到此這篇關(guān)于詳解如何在Vue2中實(shí)現(xiàn)useDraggable的文章就介紹到這了,更多相關(guān)Vue2實(shí)現(xiàn)useDraggable內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用vue實(shí)現(xiàn)猜謎卡片游戲

    使用vue實(shí)現(xiàn)猜謎卡片游戲

    這篇文章主要為大家詳細(xì)介紹了如何使用vue實(shí)現(xiàn)簡(jiǎn)單的猜謎卡片游戲,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以參考一下
    2023-09-09
  • 解決vuejs 使用value in list 循環(huán)遍歷數(shù)組出現(xiàn)警告的問題

    解決vuejs 使用value in list 循環(huán)遍歷數(shù)組出現(xiàn)警告的問題

    今天小編就為大家分享一篇解決vuejs 使用value in list 循環(huán)遍歷數(shù)組出現(xiàn)警告的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • 完美解決vue引入BMapGL is not defined的問題

    完美解決vue引入BMapGL is not defined的問題

    在Vue項(xiàng)目中使用BMapGL時(shí),通過在src下添加bmp.js文件并配置密鑰(ak),可以有效解決地圖API的應(yīng)用問題,本方法是基于個(gè)人經(jīng)驗(yàn)總結(jié),希望能為開發(fā)者提供參考和幫助
    2024-10-10
  • vue3+vite多項(xiàng)目多模塊打包(基于vite-plugin-html插件)

    vue3+vite多項(xiàng)目多模塊打包(基于vite-plugin-html插件)

    這篇文章主要給大家介紹了關(guān)于vue3+vite基于vite-plugin-html插件實(shí)現(xiàn)多項(xiàng)目多模塊打包的相關(guān)資料,現(xiàn)在很多小伙伴都已經(jīng)使用Vite+Vue3開發(fā)項(xiàng)目了,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07
  • 關(guān)于Vue中img動(dòng)態(tài)拼接src圖片地址的問題

    關(guān)于Vue中img動(dòng)態(tài)拼接src圖片地址的問題

    這篇文章主要介紹了關(guān)于Vue中img動(dòng)態(tài)拼接src圖片地址的問題,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-10-10
  • vue中mock數(shù)據(jù),模擬后臺(tái)接口實(shí)例

    vue中mock數(shù)據(jù),模擬后臺(tái)接口實(shí)例

    這篇文章主要介紹了vue中mock數(shù)據(jù),模擬后臺(tái)接口實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Vue自定義部分頁面顯示導(dǎo)航欄功能

    Vue自定義部分頁面顯示導(dǎo)航欄功能

    這篇文章主要介紹了Vue自定義部分頁面顯示導(dǎo)航欄,在設(shè)計(jì)導(dǎo)航欄的時(shí)候,考慮到登錄之前不能瀏覽該網(wǎng)站,所以需要在登錄頁和注冊(cè)頁不能出現(xiàn)導(dǎo)航欄,登錄后的頁面才能出現(xiàn)導(dǎo)航欄,本文給大家解決這個(gè)問題,感興趣的朋友跟隨小編一起看看吧
    2022-10-10
  • 解決vue組件沒顯示,沒起作用,沒報(bào)錯(cuò),但該顯示的組件沒顯示問題

    解決vue組件沒顯示,沒起作用,沒報(bào)錯(cuò),但該顯示的組件沒顯示問題

    這篇文章主要介紹了解決vue組件沒顯示,沒起作用,沒報(bào)錯(cuò),但該顯示的組件沒顯示問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Vue使用QrcodeVue生成二維碼并下載的示例代碼

    Vue使用QrcodeVue生成二維碼并下載的示例代碼

    這篇文章主要給大家介紹了Vue使用QrcodeVue生成二維碼并下載的示例,文章中有詳細(xì)的代碼示例供大家參考,感興趣的小伙伴可以參考閱讀下
    2023-08-08
  • Vue3 + Element Plus 實(shí)現(xiàn)表格全選/反選/禁用功能(示例詳解)

    Vue3 + Element Plus 實(shí)現(xiàn)表格全選/反選/禁用功能(示例詳解)

    本文詳細(xì)介紹了如何使用Vue3組合式API和ElementPlus實(shí)現(xiàn)表格的全選/反選/禁用功能,并分享了分頁保持、視覺提示優(yōu)化、性能優(yōu)化等技巧,同時(shí),還提供了常見問題的解決方案和擴(kuò)展思考,幫助開發(fā)者構(gòu)建功能豐富且用戶體驗(yàn)良好的表格組件,感興趣的朋友一起看看吧
    2025-03-03

最新評(píng)論

平顶山市| 和平县| 汉中市| 永清县| 安新县| 翼城县| 陆川县| 禹州市| 开原市| 张家川| 岳池县| 金华市| 昆明市| 河南省| 鄂托克旗| 连平县| 麻栗坡县| 鄂温| 靖远县| 许昌市| 民丰县| SHOW| 琼结县| 铜鼓县| 安阳市| 化州市| 铜陵市| 哈尔滨市| 博客| 连南| 庄浪县| 蓬莱市| 沿河| 类乌齐县| 岑巩县| 手游| 屯留县| 双桥区| 姚安县| 资阳市| 凉城县|