Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Zaawansowane dyrektywy

W NOVA LAB zaawansowane systemy interfejsów wymagają wyrafinowanych dyrektyw. Poznajmy trzy popularne wzorce: v-click-outside, v-tooltip i v-intersection.

v-click-outside

Wykrywanie kliknięcia poza elementem — idealne do zamykania menu, dropdownów i modali:

1const vClickOutside = {
2  mounted(el, binding) {
3    el._clickOutside = (event) => {
4      if (!el.contains(event.target)) {
5        binding.value(event)
6      }
7    }
8    document.addEventListener('click', el._clickOutside)
9  },
10  unmounted(el) {
11    document.removeEventListener('click', el._clickOutside)
12  }
13}

Użycie:

1<script setup>
2import { ref } from 'vue'
3
4const menuOpen = ref(false)
5
6const vClickOutside = {
7  mounted(el, binding) {
8    el._clickOutside = (event) => {
9      if (!el.contains(event.target)) {
10        binding.value(event)
11      }
12    }
13    document.addEventListener('click', el._clickOutside)
14  },
15  unmounted(el) {
16    document.removeEventListener('click', el._clickOutside)
17  }
18}
19</script>
20
21<template>
22  <div v-click-outside="() => menuOpen = false">
23    <button @click="menuOpen = !menuOpen">Menu</button>
24    <ul v-if="menuOpen">
25      <li>Opcja 1</li>
26      <li>Opcja 2</li>
27    </ul>
28  </div>
29</template>

Ważne: W hooku

unmounted
usuwamy listener, aby uniknąć wycieku pamięci. Przechowujemy referencję handlera na elemencie (
el._clickOutside
).

v-tooltip

Prosty tooltip wyświetlany po najechaniu myszką:

1const vTooltip = {
2  mounted(el, binding) {
3    const tooltip = document.createElement('div')
4    tooltip.className = 'v-tooltip'
5    tooltip.textContent = binding.value
6    tooltip.style.cssText = `
7      position: absolute; background: #333; color: white;
8      padding: 6px 12px; border-radius: 6px; font-size: 12px;
9      pointer-events: none; opacity: 0; transition: opacity 0.2s;
10      z-index: 9999;
11    `
12
13    el.style.position = 'relative'
14    el.appendChild(tooltip)
15
16    el._showTooltip = () => { tooltip.style.opacity = '1' }
17    el._hideTooltip = () => { tooltip.style.opacity = '0' }
18
19    el.addEventListener('mouseenter', el._showTooltip)
20    el.addEventListener('mouseleave', el._hideTooltip)
21  },
22  updated(el, binding) {
23    const tooltip = el.querySelector('.v-tooltip')
24    if (tooltip) tooltip.textContent = binding.value
25  },
26  unmounted(el) {
27    el.removeEventListener('mouseenter', el._showTooltip)
28    el.removeEventListener('mouseleave', el._hideTooltip)
29  }
30}

v-intersection (Intersection Observer)

Wykrywanie, kiedy element pojawia się w widoku — idealne do lazy loadingu i animacji wejścia:

1const vIntersection = {
2  mounted(el, binding) {
3    const observer = new IntersectionObserver(
4      (entries) => {
5        entries.forEach(entry => {
6          if (entry.isIntersecting) {
7            binding.value(entry)
8            if (binding.modifiers.once) {
9              observer.unobserve(el)
10            }
11          }
12        })
13      },
14      { threshold: binding.arg ? parseFloat(binding.arg) : 0.1 }
15    )
16
17    el._observer = observer
18    observer.observe(el)
19  },
20  unmounted(el) {
21    if (el._observer) {
22      el._observer.disconnect()
23    }
24  }
25}

Użycie:

1<template>
2  <div v-intersection.once="onVisible">
3    Pokaże się, gdy element będzie widoczny
4  </div>
5</template>

Cleanup — złota zasada dyrektyw

Każdy listener, observer, timer dodany w

mounted
MUSI być usunięty w
unmounted
.
Inaczej powstają wycieki pamięci.

Wzorzec:

  1. W
    mounted
    — dodaj handler i zapisz referencję na
    el
  2. W
    unmounted
    — usuń handler używając zapisanej referencji
Przejdź do CodeWorlds