使用Vue封裝一個前端通用右鍵菜單組件
本文將手把手實現(xiàn)一個基于Vue的通用的前端通用右鍵菜單,具有以下特性:
- 與業(yè)務(wù)代碼完全解耦
- 支持嵌套元素的右鍵菜單
- 菜單項可靈活配置
實現(xiàn)了一個小demo,演示地址:contextmenu-murex.vercel.app/
為什么要做右鍵菜單
筆者做過一個思維導(dǎo)圖項目,需要能夠?qū)λ季S導(dǎo)圖上的節(jié)點和畫布進(jìn)行操作,如何實現(xiàn)呢?右鍵菜單是一個不錯的選擇,既不占用畫布空間,又有豐富的功能可供選擇。但問題來了,如何實現(xiàn)這樣一個右鍵菜單:
- 組件使用方便
- 與業(yè)務(wù)代碼解耦
- 針對不同的目標(biāo)元素展示不同的右鍵菜單
- 右鍵菜單如何定位
組件的設(shè)計
比較容易想到的是:
向ContextMenu
組件傳遞一個與其關(guān)聯(lián)的容器,右擊這個容器則顯示右鍵菜單,這樣的話需要向ContextMenu
傳遞容器的真實DOM元素,這樣的方式不夠優(yōu)雅也影響效率。
<ContextMenu :relation="componentA"/>
在容器組件中嵌套ContextMenu
組件,這個方式下容器和右鍵菜單的關(guān)聯(lián)關(guān)系不明顯,而且更要命的是兩者之間產(chǎn)生了耦合,ContextMenu
依賴容器組件的數(shù)據(jù)。
<div class="componentA"> <ContextMenu :porps=""/> </div>
那么有沒有既能與業(yè)務(wù)組件解耦,且代碼組織優(yōu)雅的設(shè)計方案呢?這里筆者參考了開源組件庫里對冒泡(Popover),抽屜(Drawer),下拉菜單(Dropdown)等組件的設(shè)計方案,利用插槽將業(yè)務(wù)組件置于ContextMenu
組件中,然后是右鍵菜單的具體實現(xiàn)。
<!-- ContextMenu 組件使用 --> <!-- const menu = [ { label: '部門' }, { label: '員工' }, { label: '角色' }, { label: '權(quán)限' }, { label: '領(lǐng)導(dǎo)' } ] --> <ContextMenu :menu="menu" @select="console.log($event)"> <!-- 業(yè)務(wù)組件 --> </ContextMenu> <!-- ContextMenu --> <div ref="container"> <slot></slot> <ul class="context-menu"> <li></li> <!-- 菜單組件實現(xiàn) --> </ul> </div>
ContextMenu
的使用上,需要提供菜單配置項,是一個數(shù)組,數(shù)組元素為必須包含label
屬性的對象,選定菜單中某一項,可監(jiān)聽select
事件,然后執(zhí)行相應(yīng)的業(yè)務(wù)邏輯。
組件的布局方式
這個很容易想到,一定是要用固定定位,不管是哪個業(yè)務(wù)組件觸發(fā)了右鍵菜單,其位置一定是相對于視口的。
但問題并不是這樣就結(jié)束了,要知道默認(rèn)情況下的固定定位位置相對于視口,但如果其父代中有tranform
的元素,那么固定定位的位置是相對于這個元素的而不是視口。如果沒有想到這個特性,就會產(chǎn)生嚴(yán)重的布局問題。
我們可以利用 Vue3 內(nèi)置的<Teleport>
組件,將右鍵菜單傳送到body
元素,這樣無論如何右鍵菜單的定位位置都是相對于視口的。
<!-- ContextMenu --> <div ref="container"> <slot></slot> <Teleport to="body"> <ul class="context-menu"> <li></li> <!-- 菜單組件實現(xiàn) --> </ul> </Teleport> </div>
菜單組件的位置和可見度
設(shè)計好組件了,如何顯示組件,并定位菜單的位置呢?
這里我們可以寫一個useContextMenu
的 hook,返回位置坐標(biāo)x
和y
,以及可見度visible
,并接收一個容器參數(shù),因為需要監(jiān)聽各個需要右鍵菜單的容器的contextmenu
事件。
這里需要注意位置坐標(biāo)的要結(jié)合菜單height 和 width 來判斷是否會相對視口越界,如果越界則自適應(yīng)定位位置。
import { ref, onMounted, onUnmounted } from "vue"; export function useContextmenu(container) { const visible = ref(false); const x = ref(0); const y = ref(0); onMounted(() => { container.value.addEventListener("contextmenu", showMenu); // 把事件注冊到捕獲階段,改變觸發(fā)不同元素相同事件的觸發(fā)順序 window.addEventListener("contextmenu", hideMenu, true); window.addEventListener("click", hideMenu); }); onUnmounted(() => { container.value.removeEventListener("contextmenu", showMenu); }); function showMenu(e) { e.preventDefault(); e.stopPropagation(); visible.value = true; nextTick(() => { const { clientX, clientY } = e; const menuContainer = document.querySelector(".context-menu"); const { clientWidth: menuWidth, clientHeight: menuHeight } = menuContainer; const isOverPortWidth = clientX + menuWidth > window.innerWidth; const isOverPortHeight = clientY + menuHeight > window.innerHeight; if (isOverPortWidth) { x.value = clientX - menuWidth; y.value = clientY; } if (isOverPortHeight) { x.value = clientX; y.value = clientY - menuHeight; } if (!isOverPortHeight && !isOverPortWidth) { x.value = clientX; y.value = clientY; } }); } function hideMenu(e) { visible.value = false; } return { visible, x, y }; }
這里控制右鍵菜單的顯示和隱藏還是需要注意一些細(xì)節(jié)的,比如需要利用事件捕獲改變事件的觸發(fā)順序,以及阻止冒泡,防止嵌套元素中出現(xiàn)重復(fù)右鍵菜單。
組件動畫
這里要實現(xiàn)一個高度由 0 過渡到 h 的效果,利用<Transition>
來實現(xiàn),但有一個問題是:過渡效果是無法識別height: auto
的,也就是高度無法從 0 過渡到 auto
,那么就無法僅通過 CSS 來實現(xiàn)過渡動畫,我們可以利用<Transition>
的 JS 鉤子函數(shù),來手動計算子元素?fù)伍_的高度,然后在觸發(fā)下一次渲染更新前手動設(shè)置height
。
function handleEnter(el) { // 手動計算auto下?lián)伍_的容器高度 el.style.height = 'auto' // 這里需要減去多余的padding const h = el.clientHeight - 12 // 高度回歸為0 否則沒有過渡效果 el.style.height = 0 + 'px' // 渲染下一幀之前,復(fù)制過渡和計算出的高度 requestAnimationFrame(() => { el.style.height = h + 'px' el.style.transition = '.3s' }) } // 進(jìn)入動畫結(jié)束后,關(guān)閉過渡,否則關(guān)閉菜單時有時延 function handdleAfterEnter(el) { el.style.transition = 'none' } </script> <template> <div ref="container"> <slot></slot> <Teleport to="body"> <Transition @enter="handleEnter" @after-enter="handdleAfterEnter"> <ul class="context-menu" > <li></li> </ul> </Transition> </Teleport> </div> </template>
總結(jié)
好了,以上就是設(shè)計一個通用右鍵菜單組件的所有注意要點了,可以看到細(xì)節(jié)還是有一些的,比如:
- 組件的設(shè)計方案
- 固定定位的問題
- 事件觸發(fā)模型
- 菜單定位越界控制
- 組件的auto高度過渡動畫。
其實還有一種設(shè)計方案是函數(shù)式組件,利用 Vue API的h
函數(shù)將 SFC 渲染為VNode
,然后調(diào)用render
方法將真實dom進(jìn)行掛載,也支持菜單項的配置和業(yè)務(wù)解耦。
最后,奉上源碼:github.com/Jabinuu/contextmenu,如果有用的話歡迎 Star
以上就是使用Vue封裝一個前端通用右鍵菜單組件的詳細(xì)內(nèi)容,更多關(guān)于Vue右鍵菜單組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue.js實戰(zhàn)之使用Vuex + axios發(fā)送請求詳解
這篇文章主要給大家介紹了關(guān)于Vue.js使用Vuex與axios發(fā)送請求的相關(guān)資料,文中介紹的非常詳細(xì),相信對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-04-04Vue.config.js配置報錯ValidationError:?Invalid?options?object解
這篇文章主要給大家介紹了關(guān)于Vue.config.js配置報錯ValidationError:?Invalid?options?object的解決辦法,主要由于vue.config.js配置文件錯誤導(dǎo)致的,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02