使用 vue3 的自定義指令給 element-plus 的 el-dialog 增加拖拽功能


element-plus 提供的 el-dialog 對話框功能非常強大,只是美中不足不能通過拖拽的方式改變位置,有點小遺憾,那么怎么辦呢?我們可以通過 vue 的自定義指令來實現一個可以拖拽的對話框(el-dialog)。

拖拽演示

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

vue3 的自定義指令 directive

為啥選擇自定義指令的方式來實現呢?一個是可以方便的獲得 dom 便於操作,另一個是方便使用和封裝。

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

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

來自於官網。后面做插件的時候需要用到。

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

在開發測試的階段可以用這種方式,便於調試。插件每次修改的時候都會重新加載,而局部注冊的只需要局部更新即可。

我們可以定義一個 dialogdrag,然后在 mounted 里面實現拖拽的功能。

分析 element-plus 的 Dialog 對話框

想要實現拖拽功能,首先要了解 Dialog 對話框渲染出來的結構,然后才好針對性下手改造。
通過分析可見如下結構:

對話框結構

簡單的說,一個 div 里面放了三個 div,通過 margin(top、left) 來實現“居中”的效果。
那么也就是說我們想要實現拖拽功能的話,可以通過改變 margin-left、margin-top 的方式來。

鼠標的三個函數

提到拖拽功能,那么鼠標的三個事件 onmousedown、onmousemove、onmouseup 就必不可少了。

  • onmousedown
    鼠標按下的時候記錄光標的坐標,進入拖拽狀態。

  • onmouseup
    鼠標抬起的時候記錄光標的坐標,結束拖拽狀態。

  • onmousemove
    按住鼠標拖動的時候觸發,計算光標的偏移量,修改對話框的 margin 實現拖拽的效果。

實現代碼

本來想寫一個通用一點的,但是對話框渲染出來的結構比較復雜,似乎也不夠通用,所以先針對 el-dialog 實現拖拽功能。

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

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

      // 記錄拖拽開始的光標坐標,0 表示沒有拖拽
      let start = { x: 0, y: 0 }
      // 移動中記錄偏移量
      let move = { x: 0, y: 0 }

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

      // 鼠標移動,實時跟蹤
      domDrag.onmousemove = (e) => {
        if (start.x === 0) { // 不是拖拽狀態
          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'
      }
      // 鼠標抬起,結束拖拽
      domDrag.onmouseup = (e) => {
        move.x = e.clientX - start.x
        move.y = e.clientY - start.y

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

這樣修改有一個小問題,當窗口大小發生改變的時候,左距離不會隨之改變。

  • 記錄位置坐標和偏移量
    首先要記錄對話框的距離,然后要記錄拖拽的時候產生的偏移量。
  1. domset 可以記錄對話框的初始坐標。
  2. start 可以記錄開始拖拽的時候光標的位置。
  3. move 記錄拖拽過程中,光標移動的偏移量。
  • 按下鼠標 onmousedown
    按下鼠標,表示開始拖拽,這時候需要我們記錄光標的位置。
    另外在測試的時候發現一個小問題,當關閉對話框的時候有一個過渡動畫,然后在打開對話框進行拖拽的時候,就飛掉了。

找了一下原因后發現,在關閉的過渡動畫的時候,會把 top 改成 15vh,這樣就和我們拖拽后的 top 不一致。

所以在按下鼠標的時候需要做一個判斷。如果恢復了初始狀態,那么需要改一下 domset 的 y 坐標。x坐標不用改,因為過渡動畫沒有改 left 。

  • 移動鼠標 onmousemove
    在移動鼠標的過程中,我們可以得到光標的位置,減去初始光標位置,就是對話框要移動的距離。
    然后我們用對話框的 初始坐標 + 偏移量,就可以得到對話框的新的位置坐標。
    這樣就實現了對話框的拖拽。

  • 抬起鼠標 onmouseup
    不能一直拖拽,所以我們需要一個結束動作。
    當抬起鼠標的時候,我們可以認為是結束拖拽了,這時我們要記錄對話框的新的位置坐標,
    然后設置 start.x = 0 表示結束拖拽。

做成插件便於復用

最后我們把這個拖拽功能做成一個插件,這樣可以便於全局注冊。

建立一個js文件

// dialogDrag.js

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

export default dialogDrag

然后在 main.js 里面掛載這個插件。

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

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

使用方式

本來想直接放在 el-dialog 里面,但是卻沒有效果,所以只好在外面套上一個 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM