欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Vue封裝一個(gè)前端通用右鍵菜單組件

 更新時(shí)間:2024年02月23日 11:12:36   作者:八股哈希表  
這篇文章主要為大家詳細(xì)介紹了如何使用Vue封裝一個(gè)前端通用右鍵菜單組件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

本文將手把手實(shí)現(xiàn)一個(gè)基于Vue的通用的前端通用右鍵菜單,具有以下特性:

  • 與業(yè)務(wù)代碼完全解耦
  • 支持嵌套元素的右鍵菜單
  • 菜單項(xiàng)可靈活配置

實(shí)現(xiàn)了一個(gè)小demo,演示地址:contextmenu-murex.vercel.app/

為什么要做右鍵菜單

筆者做過(guò)一個(gè)思維導(dǎo)圖項(xiàng)目,需要能夠?qū)λ季S導(dǎo)圖上的節(jié)點(diǎn)和畫(huà)布進(jìn)行操作,如何實(shí)現(xiàn)呢?右鍵菜單是一個(gè)不錯(cuò)的選擇,既不占用畫(huà)布空間,又有豐富的功能可供選擇。但問(wèn)題來(lái)了,如何實(shí)現(xiàn)這樣一個(gè)右鍵菜單:

  • 組件使用方便
  • 與業(yè)務(wù)代碼解耦
  • 針對(duì)不同的目標(biāo)元素展示不同的右鍵菜單
  • 右鍵菜單如何定位

組件的設(shè)計(jì)

比較容易想到的是:

ContextMenu組件傳遞一個(gè)與其關(guān)聯(lián)的容器,右擊這個(gè)容器則顯示右鍵菜單,這樣的話(huà)需要向ContextMenu傳遞容器的真實(shí)DOM元素,這樣的方式不夠優(yōu)雅也影響效率。

<ContextMenu :relation="componentA"/>

在容器組件中嵌套ContextMenu組件,這個(gè)方式下容器和右鍵菜單的關(guān)聯(lián)關(guān)系不明顯,而且更要命的是兩者之間產(chǎn)生了耦合,ContextMenu依賴(lài)容器組件的數(shù)據(jù)。

<div class="componentA">
  <ContextMenu :porps=""/>
</div>

那么有沒(méi)有既能與業(yè)務(wù)組件解耦,且代碼組織優(yōu)雅的設(shè)計(jì)方案呢?這里筆者參考了開(kāi)源組件庫(kù)里對(duì)冒泡(Popover),抽屜(Drawer),下拉菜單(Dropdown)等組件的設(shè)計(jì)方案,利用插槽將業(yè)務(wù)組件置于ContextMenu組件中,然后是右鍵菜單的具體實(shí)現(xiàn)。

<!-- ContextMenu 組件使用 -->
<!-- const menu = [
  { label: '部門(mén)' },
  { 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>
    <!-- 菜單組件實(shí)現(xiàn) -->
  </ul>
</div>

ContextMenu的使用上,需要提供菜單配置項(xiàng),是一個(gè)數(shù)組,數(shù)組元素為必須包含label屬性的對(duì)象,選定菜單中某一項(xiàng),可監(jiān)聽(tīng)select事件,然后執(zhí)行相應(yīng)的業(yè)務(wù)邏輯。

組件的布局方式

這個(gè)很容易想到,一定是要用固定定位,不管是哪個(gè)業(yè)務(wù)組件觸發(fā)了右鍵菜單,其位置一定是相對(duì)于視口的。

但問(wèn)題并不是這樣就結(jié)束了,要知道默認(rèn)情況下的固定定位位置相對(duì)于視口,但如果其父代中有tranform的元素,那么固定定位的位置是相對(duì)于這個(gè)元素的而不是視口。如果沒(méi)有想到這個(gè)特性,就會(huì)產(chǎn)生嚴(yán)重的布局問(wèn)題。

我們可以利用 Vue3 內(nèi)置的<Teleport>組件,將右鍵菜單傳送到body元素,這樣無(wú)論如何右鍵菜單的定位位置都是相對(duì)于視口的。

<!-- ContextMenu -->
<div ref="container">
  <slot></slot>
  
  <Teleport to="body">
    <ul class="context-menu">
    <li></li>
    <!-- 菜單組件實(shí)現(xiàn) -->
  </ul>
  </Teleport>
</div>

菜單組件的位置和可見(jiàn)度

設(shè)計(jì)好組件了,如何顯示組件,并定位菜單的位置呢?

這里我們可以寫(xiě)一個(gè)useContextMenu的 hook,返回位置坐標(biāo)xy,以及可見(jiàn)度visible,并接收一個(gè)容器參數(shù),因?yàn)樾枰O(jiān)聽(tīng)各個(gè)需要右鍵菜單的容器的contextmenu事件。

這里需要注意位置坐標(biāo)的要結(jié)合菜單height 和 width 來(lái)判斷是否會(huì)相對(duì)視口越界,如果越界則自適應(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);
    // 把事件注冊(cè)到捕獲階段,改變觸發(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ù)右鍵菜單。

組件動(dòng)畫(huà)

這里要實(shí)現(xiàn)一個(gè)高度由 0 過(guò)渡到 h 的效果,利用<Transition>來(lái)實(shí)現(xiàn),但有一個(gè)問(wèn)題是:過(guò)渡效果是無(wú)法識(shí)別height: auto的,也就是高度無(wú)法從 0 過(guò)渡到 auto,那么就無(wú)法僅通過(guò) CSS 來(lái)實(shí)現(xiàn)過(guò)渡動(dòng)畫(huà),我們可以利用<Transition>的 JS 鉤子函數(shù),來(lái)手動(dòng)計(jì)算子元素?fù)伍_(kāi)的高度,然后在觸發(fā)下一次渲染更新前手動(dòng)設(shè)置height。

function handleEnter(el) {
  // 手動(dòng)計(jì)算auto下?lián)伍_(kāi)的容器高度
  el.style.height = 'auto'
  // 這里需要減去多余的padding
  const h = el.clientHeight - 12
  // 高度回歸為0 否則沒(méi)有過(guò)渡效果
  el.style.height = 0 + 'px'

  // 渲染下一幀之前,復(fù)制過(guò)渡和計(jì)算出的高度
  requestAnimationFrame(() => {
    el.style.height = h + 'px'
    el.style.transition = '.3s'
  })
}

  // 進(jìn)入動(dòng)畫(huà)結(jié)束后,關(guān)閉過(guò)渡,否則關(guān)閉菜單時(shí)有時(shí)延
  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è)計(jì)一個(gè)通用右鍵菜單組件的所有注意要點(diǎn)了,可以看到細(xì)節(jié)還是有一些的,比如:

  • 組件的設(shè)計(jì)方案
  • 固定定位的問(wèn)題
  • 事件觸發(fā)模型
  • 菜單定位越界控制
  • 組件的auto高度過(guò)渡動(dòng)畫(huà)。

其實(shí)還有一種設(shè)計(jì)方案是函數(shù)式組件,利用 Vue API的h函數(shù)將 SFC 渲染為VNode,然后調(diào)用render方法將真實(shí)dom進(jìn)行掛載,也支持菜單項(xiàng)的配置和業(yè)務(wù)解耦。

最后,奉上源碼:github.com/Jabinuu/contextmenu,如果有用的話(huà)歡迎 Star

以上就是使用Vue封裝一個(gè)前端通用右鍵菜單組件的詳細(xì)內(nèi)容,更多關(guān)于Vue右鍵菜單組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue3不同環(huán)境下實(shí)現(xiàn)配置代理

    vue3不同環(huán)境下實(shí)現(xiàn)配置代理

    這篇文章主要介紹了vue3不同環(huán)境下實(shí)現(xiàn)配置代理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • vue實(shí)現(xiàn)無(wú)縫輪播效果(跑馬燈)

    vue實(shí)現(xiàn)無(wú)縫輪播效果(跑馬燈)

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)無(wú)縫輪播效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • vue3安裝vant實(shí)現(xiàn)按需引入和全局引入

    vue3安裝vant實(shí)現(xiàn)按需引入和全局引入

    本文主要介紹了vue3安裝vant實(shí)現(xiàn)按需引入和全局引入,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • vue3使用vue-count-to組件的實(shí)現(xiàn)

    vue3使用vue-count-to組件的實(shí)現(xiàn)

    這篇文章主要介紹了vue3使用vue-count-to組件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • Vue可左右滑動(dòng)按鈕組組件使用詳解

    Vue可左右滑動(dòng)按鈕組組件使用詳解

    這篇文章主要為大家詳細(xì)介紹了基于Vue可左右滑動(dòng)按鈕組組件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • Vue.js劃分組件的方法

    Vue.js劃分組件的方法

    這篇文章主要介紹了Vue.js劃分組件的方法,需要的朋友可以參考下
    2017-10-10
  • Vue.js實(shí)戰(zhàn)之使用Vuex + axios發(fā)送請(qǐng)求詳解

    Vue.js實(shí)戰(zhàn)之使用Vuex + axios發(fā)送請(qǐng)求詳解

    這篇文章主要給大家介紹了關(guān)于Vue.js使用Vuex與axios發(fā)送請(qǐng)求的相關(guān)資料,文中介紹的非常詳細(xì),相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2017-04-04
  • Vue超詳細(xì)講解重試機(jī)制示例

    Vue超詳細(xì)講解重試機(jī)制示例

    這篇文章主要介紹了Vue重試機(jī)制示例,重試指的是當(dāng)加載出錯(cuò)時(shí),有能力重新發(fā)起加載組件的請(qǐng)求。異步組件加載失敗后的重試機(jī)制,與請(qǐng)求服務(wù)端接口失敗后的重試機(jī)制一樣
    2023-01-01
  • iview中Select 選擇器多選校驗(yàn)方法

    iview中Select 選擇器多選校驗(yàn)方法

    下面小編就為大家分享一篇iview中Select 選擇器多選校驗(yàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-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)致的,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-02-02

最新評(píng)論