Vue3開發(fā)右鍵菜單的示例詳解
前言
由于我個人做的項目是后臺管理項目偏多,右鍵菜單也是屬于比較高頻的組件了。但是目前我個人使用的技術(shù)棧為Vue3,目前社區(qū)還沒有很好的插件進行使用,只能被逼無奈選擇自己造輪子了。
目錄結(jié)構(gòu)基本構(gòu)成
初始階段,我把菜單組件分成兩個目錄,分別命名為ContextMenu.vue
和ContentMenuItem.vue
,兩個組件各施其職,ContextMenu.vue
組件提供最外層容器定位和層級能力,ContentMenuItem.vue
提供每項的樣式和當(dāng)前時間事件回調(diào)。
ContextMenu.vue
ContextMenu組件,我是期望在能body中進行插入,這是為了方便組件的定位(position
),那么這個時候是可以借助Vue3中的Teleport
組件實現(xiàn)該效果。由于我的業(yè)務(wù)場景是在表格中右鍵,如果我對每行(tr
)或者每個單元格(td
)都生成一個菜單組件,就會導(dǎo)致body中存在多個菜單組件。這個并不符合我的預(yù)期想法,所以我決定使用v-if
來控制組件的顯示與隱藏。 基本的HTML結(jié)構(gòu)如下:
<Teleport to="body" v-if="visible"> <div class="contextMenu" ref="contextmenuRef" > </div> </Teleport> <script lang="ts" setup> const visible = ref(false) </script> <style> .contextMenu { position: absolute; min-width: 150px; min-height:100px; padding-top: 5px; padding-bottom: 8px; background-color: #fff; border-radius: 4px; } </style>
計算ContextMenu組件的位置(position)
想要知道ContextMenu
組件會出現(xiàn)在什么位置,需要我們知道該組件中是怎么使用的?我假設(shè)有個.vue組件
<el-button @contextmenu="contextmenuFun">按鈕</el-button> <Contextmenu ref="ContextMenuRef"> </Contextmenu> import { ref } from 'vue' const ContextMenuRef = ref() const contextmenuFun = (e) => { ContextMenuRef.value.show(e) }
在業(yè)務(wù)側(cè),可以看到。我是期望有個觸發(fā)點的,無論按鈕或者HTML元素也好。這個觸發(fā)點,需要手動的去調(diào)用ContextMenu組件中show方法,并且需要把當(dāng)前的觸發(fā)事件源(event
)傳遞過去。那么我們回到ContextMenu
組件中就很容易寫出show
方法的邏輯。
const position = ref({ top: 0, left: 0 }) const style = computed(() => { return { left: position.value.left, top: position.value.top } }) const show = (e: MouseEvent) => { console.log(e, "e") e.preventDefault() visible.value = true }
那么contextMenu出現(xiàn)的位置則需要我們動態(tài)的進行計算,注意點就是出現(xiàn)的位置,我們是需要計算邊界值。
... // 計算x,y的偏移值 const calculatePosition = (axis: "X" | "Y", mousePos: number, elSize: number) => { const windowSize = axis === "X" ? window.innerWidth : window.innerHeight const scrollPos = axis === "X" ? window.scrollX : window.scrollY let pos = mousePos - scrollPos if (pos + elSize > windowSize) { pos = Math.max(0, pos - elSize) } return pos + scrollPos } const show = async (e: MouseEvent) => { e.preventDefault() visible.value = true await nextTick() const el = contextmenuRef.value if (!el) { return } const width = el.clientWidth const height = el.clientHeight const { pageX: x, pageY: y } = e position.value.top = calculatePosition("Y", y, height) position.value.left = calculatePosition("X", x, width) console.log(position.value, "w") } ...
我們通過calculatePosition
計算出有效的x,y,在用Math.max
確保顯示不會超出當(dāng)前的屏幕。
點擊菜單外部隱藏
如何判斷點擊菜單外部進行隱藏呢?這個時候,就需要借助點擊對象中的event
事件進行處理了,把處理點擊元素外圍作為一個hook進行使用并命名為useClickOutside
import { onMounted, onBeforeUnmount, Ref } from "vue" function useClickOutside(elementRef: Ref<HTMLElement | null>, callback: (event: MouseEvent) => void): void { const clickOutsideHandler = (event: MouseEvent) => { const el = elementRef.value if (!el || el === event.target || event.composedPath().includes(el)) { return } callback(event) } onMounted(() => { window.addEventListener("click", clickOutsideHandler) }) onBeforeUnmount(() => { window.removeEventListener("click", clickOutsideHandler) }) } export default useClickOutside
<div class="contextMenu" ref="contextmenuRef" :style="style">1234</div> const contextmenuRef = ref<HTMLDivElement | null>(null) import useClickOutside from "./UseClickOutSide" useClickOutside(contextmenuRef, () => { visible.value = false })
這個時候我們就能實現(xiàn)點擊菜單外部讓菜單隱藏了,但是還會伴隨一個問題,就是如果,我右鍵展開了菜單,當(dāng)我去點擊某個按鈕的時候,我不希望這個這個菜單進行隱藏,而是希望一直顯示。這個時候,就需要針對useClickOutside
添加一個額外的參數(shù)進行控制。 針對點擊某個元素,菜單不隱藏
在業(yè)務(wù)代碼中,可以通過傳遞ignore
進行HTML元素排除
div class="contextMenua" @contextmenu="contextmenu">123</div> <button class="ingoreBtn">不隱藏的按鈕</button> <ContextMenu ref="contextmenuRef" :ignore="ignore" />
在contextmenu中定義props
interface Props { ignore: string[] } const props = withDefaults(defineProps<Props>(), { ignore: () => [] as string[] }) ... useClickOutside( contextmenuRef, () => { console.log("w") visible.value = false }, { ignore: props.ignore } ) ...
在useClickOutside函數(shù)中新增IgnoreElement
方法用來排除HTML元素
let isIgnore = true const IgnoreElement = (ignore: string[], event: MouseEvent) => { return ignore.some((target) => { if (typeof target === "string") { return Array.from(window.document.querySelectorAll(target)).some( (el) => el === event.target || event.composedPath().includes(el) ) } }) } const clickOutsideHandler = (event: MouseEvent) => { ... if (options?.ignore && options.ignore.length > 0) { isIgnore = !IgnoreElement(options.ignore, event) } if (!isIgnore) { isIgnore = true return } ... }
我們通過isIgnore
變量進行打標(biāo)識,用于判斷是否經(jīng)歷過IgnoreElement
的調(diào)用,默認(rèn)為true,并不會影響現(xiàn)有邏輯。當(dāng)isIgnore
為false的時候,我們需要把它變成true,防止下次點擊無法隱藏。
菜單不隨著滾動條進行滾動
當(dāng)我們的頁面高度超出了屏幕高度時,會出現(xiàn)滾動條的情況,當(dāng)我們對某個元素進行右鍵菜單的過程會出現(xiàn),然后再去進行滾動,會發(fā)現(xiàn)我們的菜單也會跟隨著移動。為了解決這個情況,可以使用一個透明的遮蓋層蓋住body,使得原本的滾動行為失效。 在這理論上,需要對HTML結(jié)構(gòu)進行調(diào)整
<div class="contextMenu-wrapper" :class="{ 'is-fixed': fixed }"> <div class="contextMenu" ref="contextmenuRef" :style="style" :class="[popperClass]">1234</div> </div> interface Props { ignore: string[] popperClass?: string isFixed: boolean } watch( () => fixed.value, () => { if (fixed.value) { document.body.style.overflow = "hidden" } else { document.body.style.overflow = defaultSyleOverFlow.value } } ) const show = async (e: MouseEvent) => { ... fixed.value = props.isFixed ... }) useClickOutside( contextmenuRef, () => { visible.value = false fixed.value = false }, { ignore: props.ignore } ) onMounted(async () => { if (props.isFixed) { await nextTick() defaultSyleOverFlow.value = document.body.style.overflow const style = window.getComputedStyle(document.body) defaultSyleOverFlow.value = style.overflow } }) <style> .contextMenu-wrapper { z-index: 9999; background-color: transparent; &.is-fixed { position: fixed; left: 0; top: 0; width: 100%; height: 100%; } } </style>
添加了is-fixed
變量作為是否需要遮蓋層的標(biāo)識。通過watch
監(jiān)聽fixed的變化,如果為真的話,則需要body的overflow
變成hidden,關(guān)閉了的話恢復(fù)默認(rèn)的值defaultSyleOverFlow
目前為止,就已經(jīng)完成了下拉菜單的基本功能,但是還有以下功能還沒有完成:
- 響應(yīng)鍵盤事件
- 層級zIndex的控制
- 多層級菜單(subItem)
到此這篇關(guān)于Vue3開發(fā)右鍵菜單的示例詳解的文章就介紹到這了,更多相關(guān)Vue3右鍵菜單內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue在使用element組件出現(xiàn)<el-input>標(biāo)簽無法輸入的問題
這篇文章主要介紹了vue在使用element組件出現(xiàn)<el-input>標(biāo)簽無法輸入的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04Vue中用watch一次監(jiān)聽多個值變化的示例詳解
在Vue中,watch 本身不能監(jiān)聽多個變量,但我們可以通過返回具有計算屬性的對象然后監(jiān)聽該對象,從而實現(xiàn)一次性“監(jiān)聽多個變量”,本文給大家介紹了Vue中用watch一次監(jiān)聽兩個值變化的示例,需要的朋友可以參考下2024-01-01使用ElementUI el-upload實現(xiàn)一次性上傳多個文件
在日常的前端開發(fā)中,文件上傳是一個非常常見的需求,尤其是在用戶需要一次性上傳多個文件的場景下,ElementUI作為一款非常優(yōu)秀的Vue.js 2.0組件庫,為我們提供了豐富的UI組件,本文介紹了如何使用ElementUI el-upload實現(xiàn)一次性上傳多個文件,需要的朋友可以參考下2024-08-08關(guān)于vue3.0中的this.$router.replace({ path: ''/''})刷新無效果問題
這篇文章主要介紹了關(guān)于vue3.0中的this.$router.replace({ path: '/'})刷新無效果問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01vue2.x 父組件監(jiān)聽子組件事件并傳回信息的方法
本篇文章主要介紹了vue2.x 父組件監(jiān)聽子組件事件并傳回信息的方法,具有一定的參考價值,有興趣的可以了解一下2017-07-07