最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3開發(fā)右鍵菜單的示例詳解

 更新時(shí)間:2024年03月12日 10:46:17   作者:迷途小羔羊  
右鍵菜單在項(xiàng)目開發(fā)中是屬于比較高頻的組件了,所以這篇文章小編主要來和大家介紹一下如何利用vue3開發(fā)一個(gè)右鍵菜單,有需要的可以參考下

前言

由于我個(gè)人做的項(xiàng)目是后臺(tái)管理項(xiàng)目偏多,右鍵菜單也是屬于比較高頻的組件了。但是目前我個(gè)人使用的技術(shù)棧為Vue3,目前社區(qū)還沒有很好的插件進(jìn)行使用,只能被逼無奈選擇自己造輪子了。

目錄結(jié)構(gòu)基本構(gòu)成

初始階段,我把菜單組件分成兩個(gè)目錄,分別命名為ContextMenu.vueContentMenuItem.vue,兩個(gè)組件各施其職,ContextMenu.vue組件提供最外層容器定位和層級(jí)能力,ContentMenuItem.vue提供每項(xiàng)的樣式和當(dāng)前時(shí)間事件回調(diào)。

ContextMenu.vue

ContextMenu組件,我是期望在能body中進(jìn)行插入,這是為了方便組件的定位(position),那么這個(gè)時(shí)候是可以借助Vue3中的Teleport組件實(shí)現(xiàn)該效果。由于我的業(yè)務(wù)場(chǎng)景是在表格中右鍵,如果我對(duì)每行(tr)或者每個(gè)單元格(td)都生成一個(gè)菜單組件,就會(huì)導(dǎo)致body中存在多個(gè)菜單組件。這個(gè)并不符合我的預(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>

計(jì)算ContextMenu組件的位置(position)

想要知道ContextMenu組件會(huì)出現(xiàn)在什么位置,需要我們知道該組件中是怎么使用的?我假設(shè)有個(gè).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è),可以看到。我是期望有個(gè)觸發(fā)點(diǎn)的,無論按鈕或者HTML元素也好。這個(gè)觸發(fā)點(diǎn),需要手動(dòng)的去調(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)的位置則需要我們動(dòng)態(tài)的進(jìn)行計(jì)算,注意點(diǎn)就是出現(xiàn)的位置,我們是需要計(jì)算邊界值。

...
// 計(jì)算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計(jì)算出有效的x,y,在用Math.max確保顯示不會(huì)超出當(dāng)前的屏幕。

點(diǎn)擊菜單外部隱藏

如何判斷點(diǎn)擊菜單外部進(jìn)行隱藏呢?這個(gè)時(shí)候,就需要借助點(diǎn)擊對(duì)象中的event事件進(jìn)行處理了,把處理點(diǎn)擊元素外圍作為一個(gè)hook進(jìn)行使用并命名為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
})

這個(gè)時(shí)候我們就能實(shí)現(xiàn)點(diǎn)擊菜單外部讓菜單隱藏了,但是還會(huì)伴隨一個(gè)問題,就是如果,我右鍵展開了菜單,當(dāng)我去點(diǎn)擊某個(gè)按鈕的時(shí)候,我不希望這個(gè)這個(gè)菜單進(jìn)行隱藏,而是希望一直顯示。這個(gè)時(shí)候,就需要針對(duì)useClickOutside添加一個(gè)額外的參數(shù)進(jìn)行控制。 針對(duì)點(diǎn)擊某個(gè)元素,菜單不隱藏

在業(yè)務(wù)代碼中,可以通過傳遞ignore進(jìn)行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變量進(jìn)行打標(biāo)識(shí),用于判斷是否經(jīng)歷過IgnoreElement的調(diào)用,默認(rèn)為true,并不會(huì)影響現(xiàn)有邏輯。當(dāng)isIgnore為false的時(shí)候,我們需要把它變成true,防止下次點(diǎn)擊無法隱藏。

菜單不隨著滾動(dòng)條進(jìn)行滾動(dòng)

當(dāng)我們的頁面高度超出了屏幕高度時(shí),會(huì)出現(xiàn)滾動(dòng)條的情況,當(dāng)我們對(duì)某個(gè)元素進(jìn)行右鍵菜單的過程會(huì)出現(xiàn),然后再去進(jìn)行滾動(dòng),會(huì)發(fā)現(xiàn)我們的菜單也會(huì)跟隨著移動(dòng)。為了解決這個(gè)情況,可以使用一個(gè)透明的遮蓋層蓋住body,使得原本的滾動(dòng)行為失效。 在這理論上,需要對(duì)HTML結(jié)構(gòu)進(jìn)行調(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)識(shí)。通過watch監(jiān)聽fixed的變化,如果為真的話,則需要body的overflow變成hidden,關(guān)閉了的話恢復(fù)默認(rèn)的值defaultSyleOverFlow

目前為止,就已經(jīng)完成了下拉菜單的基本功能,但是還有以下功能還沒有完成:

  • 響應(yīng)鍵盤事件
  • 層級(jí)zIndex的控制
  • 多層級(jí)菜單(subItem)

到此這篇關(guān)于Vue3開發(fā)右鍵菜單的示例詳解的文章就介紹到這了,更多相關(guān)Vue3右鍵菜單內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 測(cè)試平臺(tái)開發(fā)vue組件化重構(gòu)前端代碼

    測(cè)試平臺(tái)開發(fā)vue組件化重構(gòu)前端代碼

    這篇文章主要為大家介紹了測(cè)試平臺(tái)開發(fā)vue組件化重構(gòu)前端代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Vue中用props給data賦初始值遇到的問題解決

    Vue中用props給data賦初始值遇到的問題解決

    這篇文章主要介紹了Vue中用props給data賦初始值遇到的問題解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • Vue路由傳參props解耦的三種方式小結(jié)

    Vue路由傳參props解耦的三種方式小結(jié)

    這篇文章主要介紹了Vue路由傳參props解耦的三種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • Vue2中Element?UI表單的使用詳解

    Vue2中Element?UI表單的使用詳解

    這篇文章主要為大家詳細(xì)介紹了Vue2中Element?UI表單的使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • Vue.config.js配置報(bào)錯(cuò)ValidationError:?Invalid?options?object解決辦法

    Vue.config.js配置報(bào)錯(cuò)ValidationError:?Invalid?options?object解

    這篇文章主要給大家介紹了關(guān)于Vue.config.js配置報(bào)錯(cuò)ValidationError:?Invalid?options?object的解決辦法,主要由于vue.config.js配置文件錯(cuò)誤導(dǎo)致的,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-02-02
  • 使用vue3指令封裝一個(gè)圖片預(yù)覽功能

    使用vue3指令封裝一個(gè)圖片預(yù)覽功能

    這篇文章主要為大家詳細(xì)介紹了如何使用?vue3?指令封裝一個(gè)后臺(tái)管理系統(tǒng)圖片預(yù)覽功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-01-01
  • ElementUI中el-tree節(jié)點(diǎn)的操作的實(shí)現(xiàn)

    ElementUI中el-tree節(jié)點(diǎn)的操作的實(shí)現(xiàn)

    這篇文章主要介紹了ElementUI中el-tree節(jié)點(diǎn)的操作的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • VUE2響應(yīng)式原理使用Object.defineProperty缺點(diǎn)

    VUE2響應(yīng)式原理使用Object.defineProperty缺點(diǎn)

    這篇文章主要為大家介紹了VUE2響應(yīng)式原理使用Object.defineProperty缺點(diǎn)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • vue中的路由傳值與重調(diào)本路由改變參數(shù)

    vue中的路由傳值與重調(diào)本路由改變參數(shù)

    這篇文章主要介紹了vue中的路由傳值與重調(diào)本路由改變參數(shù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • 在Vue3中實(shí)現(xiàn)緩存和復(fù)用動(dòng)態(tài)組件

    在Vue3中實(shí)現(xiàn)緩存和復(fù)用動(dòng)態(tài)組件

    本文介紹了在Vue3中使用<KeepAlive>組件和手動(dòng)管理組件實(shí)例來緩存和復(fù)用動(dòng)態(tài)組件的方法,使用<KeepAlive>可以緩存特定組件,控制緩存實(shí)例數(shù)量,并通過生命周期鉤子執(zhí)行相應(yīng)邏輯,手動(dòng)管理則通過對(duì)象存儲(chǔ)組件實(shí)例,提高性能,最后,將邏輯封裝到組合式函數(shù)中,增強(qiáng)代碼復(fù)用性
    2026-04-04

最新評(píng)論

永修县| 容城县| 清远市| 观塘区| 盐城市| 灵台县| 金昌市| 潼关县| 抚州市| 扎鲁特旗| 许昌县| 景洪市| 卢氏县| 伊宁市| 许昌市| 永胜县| 固阳县| 信丰县| 皮山县| 莲花县| 马山县| 南开区| 磴口县| 全椒县| 巧家县| 巴彦淖尔市| 西青区| 辛集市| 黔南| 抚松县| 洪洞县| 襄樊市| 林西县| 轮台县| 铅山县| 东乡县| 嘉善县| 澜沧| 昭平县| 正蓝旗| 且末县|