Skip to content

虛擬元素 v0.11+

使用 Floating UI 虛擬元素 功能定位的元件,可以讓浮動元素相對於虛擬元素來定位,常用於實現右鍵選單、跟隨鼠標等功能。

右鍵選單

<FloatContextMenu> 元件是一個無樣式的右鍵選單元件,可以自己設計右鍵選單的內容,如果要用 <Menu> 元件的話,還要把 <MenuItems>static 開啟:

html
<FloatContextMenu
  v-slot="{ close }"
  enter="transition-opacity duration-200 ease-out"
  enter-from="opacity-0"
  enter-to="opacity-100"
>
  <Menu>
    <MenuItems static class="w-48 bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
      ...
    </MenuItems>
  </Menu>
</FloatContextMenu>

<script setup>
import { FloatContextMenu } from '@headlessui-float/vue'
</script>

跟隨鼠標

<FloatCursor> 元件提供了跟隨鼠標定位功能,可以讓任何元素跟隨鼠標移動,同時也支援觸控移動:

html
<FloatCursor as="div">
  <div class="w-5 h-5 bg-emerald-500 rounded-full -translate-x-1/2 -translate-y-1/2" />
</FloatCursor>

<script setup>
import { FloatCursor } from '@headlessui-float/vue'
</script>

預設 <FloatCursor> 會增加 CSS 來隱藏鼠標,如果要關掉這個功能的話,可以將 global-hide-cursor 設為 false

html
<FloatCursor :global-hide-cursor="false">

自訂虛擬元素

當然也可以使用 <FloatVirtual> 來自訂自己的虛擬元素元件,比如這裡用 <FloatVirtual> 來做一個定位到右鍵選單的元件,先定義一個 @initial 事件,可以在這裡初始化虛擬元素:

html
<FloatVirtual @initial="onInitial">
  <div>context menu content</div>
</FloatVirtual>

<script setup>
import { FloatVirtual } from '@headlessui-float/vue'
</script>

然後可以在 props 裡解構出 showreferencefloatingshow 是當前元件顯示或隱藏的狀態,reference 可以設定參考元素,不過是用虛擬元素的設定方式。虛擬元素需要包含一個 getBoundingClientRect() 方法,裡面可以自訂元素尺寸、座標位置等。因為是可以自訂位置的元素,通常都會搭配鼠標相關的事件,像這裡我們要作右鍵選單,可以綁定 contextmenu 事件:

js
import { watchEffect } from 'vue'

function onInitial({ show, reference, floating }) {
  function onContextMenu(e) {
    e.preventDefault()

    reference.value = {
      getBoundingClientRect() {
        return {
          width: 0,
          height: 0,
          x: e.clientX,
          y: e.clientY,
          top: e.clientY,
          left: e.clientX,
          right: e.clientX,
          bottom: e.clientY,
        }
      },
    }

    show.value = true
  }

  watchEffect(onInvalidate => {
    document.addEventListener('contextmenu', onContextMenu)
    onInvalidate(() => document.removeEventListener('contextmenu', onContextMenu))
  })
}

當然在點擊右鍵選單之外的地方時,需要設定可以關閉選單:

js
import { computed } from 'vue'
import { useOutsideClick } from '@headlessui-float/vue'

function onInitial({ show, reference, floating }) {
  ...
  useOutsideClick(floating, () => {
    show.value = false
  }, computed(() => show.value))
}

為了讓右鍵選單不會受其他 z-index 的影響,可以把 portal 開啟,渲染到 <body> 的底部:

html
<FloatVirtual portal @initial="onInitial">

最後放上一個簡易的右鍵選單虛擬元素範例:

vue
<template>
  <FloatVirtual portal @initial="onInitial">
    <div>context menu content</div>
  </FloatVirtual>
</template>

<script setup>
import { computed, watchEffect } from 'vue'
import { FloatVirtual, useOutsideClick } from '@headlessui-float/vue'

function onInitial({ show, reference, floating }) {
  function onContextMenu(e) {
    e.preventDefault()

    reference.value = {
      getBoundingClientRect() {
        return {
          width: 0,
          height: 0,
          x: e.clientX,
          y: e.clientY,
          top: e.clientY,
          left: e.clientX,
          right: e.clientX,
          bottom: e.clientY,
        }
      },
    }

    show.value = true
  }

  watchEffect(onInvalidate => {
    document.addEventListener('contextmenu', onContextMenu)
    onInvalidate(() => document.removeEventListener('contextmenu', onContextMenu))
  })

  useOutsideClick(floating, () => {
    show.value = false
  }, computed(() => show.value))
}
</script>

虛擬元素顯示

<FloatVirtual> 除了可以在內部修改顯示之外,也可以由外部來做控制:

vue
<template>
  <FloatVirtual :show="show">
    ...
  </FloatVirtual>
</template>

<script setup>
import { ref } from 'vue'

const show = ref(false)

show.value = true
</script>

Released under the MIT License.