封裝第三方組件(12)使用vue3的自定義指令給 el-dialog 增加拖拽功能

element-plus 提供的 el-dialog 對(duì)話框功能非常強(qiáng)大,只是美中不足不能通過(guò)拖拽的方式改變位置,有點(diǎn)小遺憾,那么怎么辦呢?我們可以通過(guò) vue 的自定義指令來(lái)實(shí)現(xiàn)一個(gè)可以拖拽的對(duì)話框(el-dialog)。

先看看拖拽效果

https://www.zhihu.com/zvideo/1380450791975731200

vue 的自定義指令 directive

為啥選擇自定義指令的方式來(lái)實(shí)現(xiàn)呢?一個(gè)是可以方便的獲得 dom 便于操作,另一個(gè)是方便使用和封裝。

自定義指令有兩種注冊(cè)方式,一個(gè)是全局注冊(cè),一個(gè)是局部注冊(cè)。

  • 全局注冊(cè)自定義指令
app.directive('focus', {
  // 當(dāng)被綁定的元素插入到 DOM 中時(shí)……
  mounted(el) {
    // Focus the element
    el.focus()
  }
})

來(lái)自于官網(wǎng)。后面做插件的時(shí)候需要用到。

  • 局部注冊(cè)自定義指令
directives: {
  focus: {
    // 指令的定義
    mounted(el) {
      el.focus()
    }
  }
}

在開(kāi)發(fā)測(cè)試的階段可以用這種方式,便于調(diào)試。插件每次修改的時(shí)候都會(huì)重新加載,而局部注冊(cè)的只需要局部更新即可。

我們可以定義一個(gè) dialogdrag,然后在 mounted 里面實(shí)現(xiàn)拖拽的功能。

分析 element-plus 的 Dialog 對(duì)話框

想要實(shí)現(xiàn)拖拽功能,首先要了解 Dialog 對(duì)話框渲染出來(lái)的結(jié)構(gòu),然后才好針對(duì)性下手改造。
通過(guò)分析可見(jiàn)如下結(jié)構(gòu):

對(duì)話框結(jié)構(gòu)

簡(jiǎn)單的說(shuō),一個(gè) div 里面放了三個(gè) div,通過(guò) margin(top、left) 來(lái)實(shí)現(xiàn)“居中”的效果。
那么也就是說(shuō)我們想要實(shí)現(xiàn)拖拽功能的話,可以通過(guò)改變 margin-left、margin-top 的方式來(lái)。

鼠標(biāo)的三個(gè)函數(shù)

提到拖拽功能,那么鼠標(biāo)的三個(gè)事件 onmousedown、onmousemove、onmouseup 就必不可少了。

  • onmousedown
    鼠標(biāo)按下的時(shí)候記錄光標(biāo)的坐標(biāo),進(jìn)入拖拽狀態(tài)。

  • onmouseup
    鼠標(biāo)抬起的時(shí)候記錄光標(biāo)的坐標(biāo),結(jié)束拖拽狀態(tài)。

  • onmousemove
    按住鼠標(biāo)拖動(dòng)的時(shí)候觸發(fā),計(jì)算光標(biāo)的偏移量,修改對(duì)話框的 margin 實(shí)現(xiàn)拖拽的效果。

實(shí)現(xiàn)代碼

本來(lái)想寫(xiě)一個(gè)通用一點(diǎn)的,但是對(duì)話框渲染出來(lái)的結(jié)構(gòu)比較復(fù)雜,似乎也不夠通用,所以先針對(duì) el-dialog 實(shí)現(xiàn)拖拽功能。

  app.directive('dialogdrag', {
    // 渲染完畢
    mounted(el, binding) {
      // binding.arg
      // binding.value
      // 可視窗口的寬度
      const clientWidth = document.documentElement.clientWidth
      // 可視窗口的高度
      const clientHeight = document.documentElement.clientHeight
      // 記錄坐標(biāo)
      let domset = {
        x: clientWidth / 4, // 默認(rèn)width 50%
        y: clientHeight * 15 / 100  // 根據(jù) 15vh 計(jì)算
      }

      // 彈窗的容器
      const domDrag = el.firstElementChild.firstElementChild
      // 重新設(shè)置上、左距離
      domDrag.style.marginTop = domset.y + 'px'
      domDrag.style.marginLeft = domset.x + 'px'

      // 記錄拖拽開(kāi)始的光標(biāo)坐標(biāo),0 表示沒(méi)有拖拽
      let start = { x: 0, y: 0 }
      // 移動(dòng)中記錄偏移量
      let move = { x: 0, y: 0 }

      // 鼠標(biāo)按下,開(kāi)始拖拽
      domDrag.onmousedown = (e) => {
        // 判斷對(duì)話框是否重新打開(kāi)
        if (domDrag.style.marginTop === '15vh') {
          // 重新打開(kāi),設(shè)置 domset.y  top
          domset.y = clientHeight * 15 / 100
        }
        start.x = e.clientX
        start.y = e.clientY
        domDrag.style.cursor = 'move' // 改變光標(biāo)形狀
      }

      // 鼠標(biāo)移動(dòng),實(shí)時(shí)跟蹤
      domDrag.onmousemove = (e) => {
        if (start.x === 0) { // 不是拖拽狀態(tài)
          return
        }
        move.x = e.clientX - start.x
        move.y = e.clientY - start.y

        // 初始位置 + 拖拽距離
        domDrag.style.marginLeft = ( domset.x + move.x )  + 'px'
        domDrag.style.marginTop = ( domset.y + move.y )  + 'px'
      }
      // 鼠標(biāo)抬起,結(jié)束拖拽
      domDrag.onmouseup = (e) => {
        move.x = e.clientX - start.x
        move.y = e.clientY - start.y

        // 記錄新坐標(biāo),作為下次拖拽的初始位置
        domset.x += move.x
        domset.y += move.y
        domDrag.style.cursor = '' // 恢復(fù)光標(biāo)形狀
        domDrag.style.marginLeft = domset.x + 'px'
        domDrag.style.marginTop = domset.y + 'px'
        // 結(jié)束拖拽
        start.x = 0
      }
    }
  })
  • 重新定位對(duì)話框
    默認(rèn)的 top 是15vh,也就是距離頂部 15% ,所以需要 用 clientHeight * 15 / 100 轉(zhuǎn)換為像素。
    默認(rèn)的 left 是 50%,所以需要用 clientWidth / 4 轉(zhuǎn)換為像素。

這樣修改有一個(gè)小問(wèn)題,當(dāng)窗口大小發(fā)生改變的時(shí)候,左距離不會(huì)隨之改變。

  • 記錄位置坐標(biāo)和偏移量
    首先要記錄對(duì)話框的距離,然后要記錄拖拽的時(shí)候產(chǎn)生的偏移量。
  1. domset 可以記錄對(duì)話框的初始坐標(biāo)。
  2. start 可以記錄開(kāi)始拖拽的時(shí)候光標(biāo)的位置。
  3. move 記錄拖拽過(guò)程中,光標(biāo)移動(dòng)的偏移量。
  • 按下鼠標(biāo) onmousedown
    按下鼠標(biāo),表示開(kāi)始拖拽,這時(shí)候需要我們記錄光標(biāo)的位置。
    另外在測(cè)試的時(shí)候發(fā)現(xiàn)一個(gè)小問(wèn)題,當(dāng)關(guān)閉對(duì)話框的時(shí)候有一個(gè)過(guò)渡動(dòng)畫(huà),然后在打開(kāi)對(duì)話框進(jìn)行拖拽的時(shí)候,就飛掉了。

找了一下原因后發(fā)現(xiàn),在關(guān)閉的過(guò)渡動(dòng)畫(huà)的時(shí)候,會(huì)把 top 改成 15vh,這樣就和我們拖拽后的 top 不一致。

所以在按下鼠標(biāo)的時(shí)候需要做一個(gè)判斷。如果恢復(fù)了初始狀態(tài),那么需要改一下 domset 的 y 坐標(biāo)。x坐標(biāo)不用改,因?yàn)檫^(guò)渡動(dòng)畫(huà)沒(méi)有改 left 。

  • 移動(dòng)鼠標(biāo) onmousemove
    在移動(dòng)鼠標(biāo)的過(guò)程中,我們可以得到光標(biāo)的位置,減去初始光標(biāo)位置,就是對(duì)話框要移動(dòng)的距離。
    然后我們用對(duì)話框的 初始坐標(biāo) + 偏移量,就可以得到對(duì)話框的新的位置坐標(biāo)。
    這樣就實(shí)現(xiàn)了對(duì)話框的拖拽。

  • 抬起鼠標(biāo) onmouseup
    不能一直拖拽,所以我們需要一個(gè)結(jié)束動(dòng)作。
    當(dāng)抬起鼠標(biāo)的時(shí)候,我們可以認(rèn)為是結(jié)束拖拽了,這時(shí)我們要記錄對(duì)話框的新的位置坐標(biāo),
    然后設(shè)置 start.x = 0 表示結(jié)束拖拽。

做成插件便于復(fù)用

最后我們把這個(gè)拖拽功能做成一個(gè)插件,這樣可以便于全局注冊(cè)。

建立一個(gè)js文件

// dialogDrag.js

const dialogDrag = (app, options) => {
  app.directive('dialogdrag', {
    // 指令的定義
    mounted(el, binding) {
    同上,略...
}

export default dialogDrag

然后在 main.js 里面掛載這個(gè)插件。

// 拖拽
import dialogDrag from './control-web/js/dialogDrag.js'

createApp(App).use(dialogDrag) // 對(duì)話框的拖拽

使用方式

本來(lái)想直接放在 el-dialog 里面,但是卻沒(méi)有效果,所以只好在外面套上一個(gè) div。

<div v-dialogdrag>
    <el-dialog
      title="收貨地址"
      v-model="dialogFormVisible"
      :modal="false"
    >
     略...
    </el-dialog>
  </div>

注意,要加上 v- ,即 v-dialogdrag。

源碼

https://gitee.com/naturefw/nf-vite2-element
/src/control-web/js/dialogDrag.js

https://gitee.com/naturefw/nf-vite2-element/tree/master/src/control-web/js

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容