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í)間再長(zhǎng)也無用,所以只好監(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>拖拽測(cè)試</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-04vue循環(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