這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 下圖這個情景,你是否也遇到過? 當你右鍵點擊網頁上的某個元素時,彈出的菜單被屏幕邊緣遮擋了,導致你無法看清或選擇菜單項? 上圖中右鍵菜單的選項並不是固定不變的,它會根據不同的元素或場景來顯示不同的選項。 也就是說,菜單的內容和大小都是動態 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
下圖這個情景,你是否也遇到過?
當你右鍵點擊網頁上的某個元素時,彈出的菜單被屏幕邊緣遮擋了,導致你無法看清或選擇菜單項?
上圖中右鍵菜單的選項並不是固定不變的,它會根據不同的元素或場景來顯示不同的選項。
也就是說,菜單的內容和大小都是動態生成的,而不是預先設定好的。
這就給我們調整菜單位置帶來了一定的難度,不過當你看完這篇文章所有的問題都不再是問題。
分析問題
遇事不決先畫圖,我們要解決的問題本質上就是菜單生成的位置,所以我們畫個圖來找一下頭緒:
我們通過上圖可以知道,菜單能否在視口中放得下,取決於兩個條件:
windowW(視口寬度) - mouseX(滑鼠 x 坐標) > menuW(菜單寬度)
windowH(視口高度) - mouseY(滑鼠 y 坐標) > menuH(菜單高度)
當同時滿足這兩個條件的時候說明菜單放得下,那我們就要思考如果不滿足條件的時候怎麼辦了。
如果不滿足條件一說明寬度放不下,那我們就讓菜單生成到滑鼠的左邊 mouseX - menuW
,就像下圖這樣。
如果不滿足條件二說明高度放不下,那我們就讓菜單貼底 windowH - menuH
,像這樣。
那如果兩個條件都不滿足,就同時應用兩個解決辦法。
解決問題
先來看一下現在的代碼:
<template> <div ref="containerRef"> <slot></slot> <Teleport to="body"> <div v-if="showMenu" class="context-menu" :style="{ left: mouseX + 'px', top: mouseY + 'px', }"> <div class="menu-list"> <div @click="handleClick(item)" class="menu-item" v-for="(item, i) in menu" :key="item.label"> {{ item.label }} </div> </div> </div> </Teleport> </div> </template> <script setup> import { ref } from 'vue'; import useContextMenu from './useContextMenu'; const props = defineProps({ menu: { type: Array, default: () => [], }, }); const containerRef = ref(null); const { mouseX, mouseY, showMenu } = useContextMenu(containerRef); function handleClick() { showMenu.value = false; } </script>
看到我們現在是直接將滑鼠的坐標賦值給了菜單,那麼接下來就要給菜單一個經過計算的合適位置。
我們知道視口的大小、滑鼠的位置、菜單的大小都是會變化的,所以這幾個數據都要是響應式。
現在僅僅知道滑鼠的位置,還需要知道視口與菜單的大小。
視口大小我們寫一個函數來監聽視口大小的變化:
import { ref } from "vue"; const windowW = ref(document.documentElement.clientWidth); const windowH = ref(document.documentElement.clientHeight); window.addEventListener("resize", () => { windowW.value = document.documentElement.clientWidth; windowH.value = document.documentElement.clientHeight; }); export default function () { return { windowW, windowH, }; }而菜單的大小可以利用之前寫過的一個自定義指令來監聽菜單大小的變化,代碼如下:
const map = new WeakMap(); const ob = new ResizeObserver((entries) => { for (const entry of entries) { // 這個元素對應的回調函數? const handler = map.get(entry.target); if (handler) { const box = entry.borderBoxSize[0]; handler({ width: box.inlineSize, height: box.blockSize, }); } } }); export default { mounted(el, binding) { // 監視尺寸變化 ob.observe(el); map.set(el, binding.value); }, unmounted(el) { // 取消監聽 ob.unobserve(el); }, };
現在這些值我們都已經知道了,我們去實現一下。
<template> <div ref="containerRef"> <slot></slot> <Teleport to="body"> <!-- 將計算好的位置賦值給菜單 --> <div v-if="showMenu" class="context-menu" :style="{ left: pos.posX + 'px', top: pos.posY + 'px', }"> <!-- 指令為全局指令,在菜單上使用指令來監聽菜單尺寸的變化並觸發函數 --> <div v-size-ob="handleSize" class="menu-list"> <div @click="handleClick(item)" class="menu-item" v-for="(item, i) in menu" :key="item.label"> {{ item.label }} </div> </div> </div> </Teleport> </div> </template> <script setup> import { ref } from 'vue'; import useContextMenu from './useContextMenu'; import { computed } from '@vue/reactivity'; // 引入監聽視口大小的函數 import useViewport from './useViewport'; const props = defineProps({ menu: { type: Array, default: () => [], }, }); const containerRef = ref(null); const { mouseX, mouseY, showMenu } = useContextMenu(containerRef); // 聲明兩個響應式變數,用來記錄菜單大小的變化。 const menuW = ref(0); const menuH = ref(0); function handleSize({ width, height }) { menuW.value = width; menuH.value = height; } // 獲得視口的大小 const { windowW, windowH } = useViewport(); // 計算屬性,用來計算菜單合適的位置 const pos = computed(() => { let posX = mouseX.value; let posY = mouseY.value; // 寬度放不下生成新的位置 if (mouseX.value > windowW.value - menuW.value) { posX = mouseX.value - menuW.value } // 高度放不下生成新的位置 if (mouseY.value > windowH.value - menuH.value) { posY = windowH.value - menuH.value } return { posX, posY, }; }); function handleClick() { showMenu.value = false; } </script>
我們現在來看一下效果如何。
效果完美!
總結
這樣,我們就實現了一個簡單的右鍵菜單,它可以根據滑鼠的位置和視口的大小自動調整菜單的位置,避免被遮擋。
這個功能雖然看起來不起眼,但是卻能提高用戶的體驗和操作的便捷性。
當然,這個功能還有很多可以改進的地方,比如菜單的樣式、動畫、交互等等。