W NOVA LAB zaawansowane systemy interfejsów wymagają wyrafinowanych dyrektyw. Poznajmy trzy popularne wzorce: v-click-outside, v-tooltip i v-intersection.
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).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}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>Każdy listener, observer, timer dodany w
MUSI być usunięty w mounted
. Inaczej powstają wycieki pamięci.unmounted
Wzorzec:
mounted — dodaj handler i zapisz referencję na elunmounted — usuń handler używając zapisanej referencji