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 轉換為畫素。
這樣修改有一個小問題,當視窗大小發生改變的時候,左距離不會隨之改變。
- 記錄位置座標和偏移量
首先要記錄對話方塊的距離,然後要記錄拖拽的時候產生的偏移量。
- domset 可以記錄對話方塊的初始座標。
- start 可以記錄開始拖拽的時候游標的位置。
- 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