vue3使用自定義指令實(shí)現(xiàn)el dialog拖拽功能示例詳解
實(shí)現(xiàn)el-dialog的拖拽功能
這里指的是 element-plus 的el-dialog組件,一開始該組件并沒有實(shí)現(xiàn)拖拽的功能,當(dāng)然現(xiàn)在可以通過設(shè)置屬性的方式實(shí)現(xiàn)拖拽。
自帶的拖拽功能非常嚴(yán)謹(jǐn),拖拽時(shí)判斷是否拖拽出窗口,如果出去了會(huì)阻止拖拽。
如果自帶的拖拽功能可以滿足需求的話,可以跳過本文。
通過自定義指令實(shí)現(xiàn)拖拽功能
因?yàn)橐约翰僮鱠om(設(shè)置事件),所以感覺還是使用自定義指令更直接一些,而且對(duì)原生組件的影響更小。
我們先定義一個(gè)自定義指令 _dialogDrag:
import dialogDrag from './_dialog-drag'
import { watch } from 'vue'
const _dialogDrag = {
// mounted
mounted (el: any, binding: any) {
// 監(jiān)聽 dialog 是否顯示的狀態(tài)
watch (binding.value, () => {
// dialog 不可見,退出
if (!binding.value.visible) return
// 尋找 el-dialog 組件
const container = el.firstElementChild.firstElementChild
// 已經(jīng)設(shè)置拖拽事件,退出
if (container.onmousemove) return
// 等待 DOM 渲染完畢
setTimeout(() => {
// 拖拽的 “句柄”
const _dialogTitle = el.getElementsByClassName('el-dialog__header')
if (_dialogTitle.length === 0) {
// 還沒有渲染完畢,或則其他原因
console.warn('沒有找到要拖拽的 el-dialog', el)
} else {
const { setDialog } = dialogDrag()
const dialogTitle = _dialogTitle[0]
// 彈窗
const dialog = el.firstElementChild.firstElementChild.firstElementChild
// 通過 css 尋找 el-dialog 設(shè)置的寬度
const arr = dialog.style.cssText.split(';')
const width = arr[0].replace('%', '').replace('--el-dialog-width:', '') //
// 設(shè)置 el-dialog 組件、彈窗、句柄、寬度
setDialog(container, dialog, dialogTitle, width)
}
},300)
})
},
}
/**
* 注冊(cè)拖拽 dialog 的自定義指令
* @param app
* @param options
*/
const install = (app: any, options: any) => {
app.directive('dialogDrag', _dialogDrag)
}
export {
_dialogDrag,
install
}
這里有兩個(gè)比較煩人的地方:
- DOM渲染完畢的時(shí)機(jī)。執(zhí)行 mounted 的時(shí)候,DOM不一定渲染完畢,如果不使用 setTimeout 的話,就會(huì)找不到DOM,所以用了這種笨辦法。
- dialog 的隱藏。一般情況下,el-dialog 初始是隱藏狀態(tài),隱藏了就意味著DOM并不會(huì)被渲染出來??墒亲远x指令會(huì)在一開始即被執(zhí)行,這時(shí) setTimeout 的等待時(shí)間再長也無用,所以只好監(jiān)聽dialog的狀態(tài)。
- ref 通過 template 傳遞后,再次傳入組件的話,就會(huì)失去ref的那一層的響應(yīng)性,所以只能傳入reactive才行,這樣調(diào)用指令的組件,就會(huì)比較別扭,目前沒有想到更好的實(shí)現(xiàn)方式。
實(shí)現(xiàn)拖拽功能
定義指令和實(shí)現(xiàn)拖拽,我分成了兩個(gè)文件,我想,盡量解耦一下。
定義一個(gè)拖拽函數(shù)(dialogDrag):
/**
* 拖拽 dialog 的函數(shù),目前支持 element-plus
*/
export default function dialogDrag () {
/**
* 設(shè)置拖拽事件
* @param container 大容器,比如蒙版。
* @param dialog 被拖拽的窗口
* @param dialogTitle 拖拽的標(biāo)題
* @param width 寬度比例
*/
const setDialog = (container: any, dialog: any, dialogTitle: any, width: number) => {
const oldCursor = dialogTitle.style.cursor
// 可視窗口的寬度
const clientWidth = document.documentElement.clientWidth
// 可視窗口的高度
const clientHeight = document.documentElement.clientHeight
// 根據(jù)百分?jǐn)?shù)計(jì)算寬度
const tmpWidth = clientWidth * (100 - width) / 200
// 默認(rèn)寬度和高度
const domset = {
x: tmpWidth,
y: clientHeight * 15 / 100 // 根據(jù) 15vh 計(jì)算
}
// 查看dialog 當(dāng)前的寬度和高低
if (dialog.style.marginLeft === '') {
dialog.style.marginLeft = domset.x + 'px'
} else {
domset.x = dialog.style.marginLeft.replace('px','') * 1
}
if (dialog.style.marginTop === '') {
dialog.style.marginTop = domset.y + 'px'
} else {
domset.y = dialog.style.marginTop.replace('px','') * 1
}
// 記錄拖拽開始的光標(biāo)坐標(biāo),0 表示沒有拖拽
const start = { x: 0, y: 0 }
// 移動(dòng)中記錄偏移量
const move = { x: 0, y: 0 }
// 經(jīng)過時(shí)改變鼠標(biāo)指針形狀
dialogTitle.onmouseover = () => {
dialogTitle.style.cursor = 'move' // 改變光標(biāo)形狀
}
// 鼠標(biāo)按下,開始拖拽
dialogTitle.onmousedown = (e: any) => {
start.x = e.clientX
start.y = e.clientY
dialogTitle.style.cursor = 'move' // 改變光標(biāo)形狀
}
// 鼠標(biāo)移動(dòng),實(shí)時(shí)跟蹤 dialog
container.onmousemove = (e: any) => {
if (start.x === 0) { // 不是拖拽狀態(tài)
return
}
move.x = e.clientX - start.x
move.y = e.clientY - start.y
// 初始位置 + 拖拽距離
dialog.style.marginLeft = (domset.x + move.x) + 'px'
dialog.style.marginTop = (domset.y + move.y) + 'px'
}
// 鼠標(biāo)抬起,結(jié)束拖拽
container.onmouseup = (e: any) => {
if (start.x === 0) { // 不是拖拽狀態(tài)
return
}
move.x = e.clientX - start.x
move.y = e.clientY - start.y
// 記錄新坐標(biāo),作為下次拖拽的初始位置
domset.x += move.x
domset.y += move.y
dialogTitle.style.cursor = oldCursor
dialog.style.marginLeft = domset.x + 'px'
dialog.style.marginTop = domset.y + 'px'
// 結(jié)束拖拽
start.x = 0
}
}
return {
setDialog // 設(shè)置
}
}
首先觀察el-dialog渲染后的DOM結(jié)構(gòu),發(fā)現(xiàn)是通過 marginLeft、marginTop 這兩個(gè)css 的屬性,那么我們的拖拽也可以通過修改這兩個(gè)屬性來實(shí)現(xiàn)。
然后就是古老的拖拽思路:按下鼠標(biāo)的時(shí)候,記錄光標(biāo)的初始坐標(biāo),抬起鼠標(biāo)的時(shí)候,記錄光標(biāo)的結(jié)束坐標(biāo),然后計(jì)算一下得到x、y的“偏移量”,進(jìn)而修改 marginLeft、marginTop 這兩個(gè)屬性,即可實(shí)現(xiàn)拖拽的效果。
核心思路就是這樣,剩下的就是細(xì)節(jié)完善了。
還有一個(gè)小問題,拖拽后關(guān)閉,然后再次打開,希望可以在拖拽結(jié)束的地方打開,而不是默認(rèn)的位置。所以又想了個(gè)辦法記錄這個(gè)位置。
還是要觀察 el-dialog 的行為,最后發(fā)現(xiàn)規(guī)律,一開始 marginLeft 是空的,而拖拽后會(huì)保留位置。所以,判斷一下就好。
使用方式
原本想直接給el-dialog 設(shè)置自定義指令,但是發(fā)現(xiàn)“無效”,所以只好在外面套個(gè)div。
<template>
<!--拖拽-->
<el-button @click="dialog.visible = true">打開</el-button>
<div v-dialog-drag="dialog" >
<el-dialog
v-model="dialog.visible"
title="自定義拖拽2"
width="25%"
>
<span>拖拽測試</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialog.visible = false">Cancel</el-button>
<el-button type="primary" @click="dialog.visible = false">Confirm</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from 'vue'
import { _dialogDrag } from '../../../lib/main'
export default defineComponent({
name: 'nf-dialog-move',
directives: {
dialogDrag: _dialogDrag
},
props: {
},
setup(props, context) {
const dialog = reactive({
visible: false
})
return {
meta,
dialog
}
}
})
</script>
如果全局注冊(cè)了自定義指令,那么組件里面就不用注冊(cè)了。
dialog 的 visible: visible 這個(gè)屬性的名稱被寫死了,不能用其他名稱。這是一個(gè)偷懶的設(shè)定。
源碼
在線演示
naturefw-code.gitee.io/nf-rollup-u…
以上就是vue3使用自定義指令實(shí)現(xiàn)el dialog拖拽功能示例詳解的詳細(xì)內(nèi)容,更多關(guān)于vue3指令el dialog拖拽的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
區(qū)分vue-router的hash和history模式
這篇文章主要介紹了區(qū)分vue-router的hash和history模式,幫助大家更好的理解和學(xué)習(xí)vue路由,感興趣的朋友可以了解下2020-10-10
基于vue項(xiàng)目設(shè)置resolves.alias: ''@''路徑并適配webstorm
這篇文章主要介紹了基于vue項(xiàng)目設(shè)置resolves.alias: '@'路徑并適配webstorm,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12
詳解VUE Element-UI多級(jí)菜單動(dòng)態(tài)渲染的組件
這篇文章主要介紹了VUE Element-UI多級(jí)菜單動(dòng)態(tài)渲染的組件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
vue循環(huán)數(shù)組改變點(diǎn)擊文字的顏色
這篇文章主要為大家詳細(xì)介紹了vue循環(huán)數(shù)組改變點(diǎn)擊文字的顏色,非常實(shí)用的切換效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
關(guān)于vue3 vuex4 store的響應(yīng)式取值問題解決
這篇文章主要介紹了vue3 vuex4 store的響應(yīng)式取值問題,在實(shí)際生活中遇到這樣一個(gè)問題:在頁面中點(diǎn)擊按鈕,數(shù)量增加,值是存在store中的,點(diǎn)擊事件值沒變,如何解決這個(gè)問題,本文給大家分享解決方法,需要的朋友可以參考下2022-08-08

