Cours Vue.js · Module 14: Directives et plugins
Directives avancées
Dans cette leçon4
Dans NOVA LAB, les systèmes d'interface avancés exigent des directives sophistiquées. Explorons trois patterns populaires : v-click-outside, v-tooltip et v-intersection.
v-click-outside
Détecter les clics en dehors d'un élément - idéal pour fermer les menus, les listes déroulantes et les modales :
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}Utilisation :
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>Option 1</li>
26 <li>Option 2</li>
27 </ul>
28 </div>
29</template>Important : dans le hook unmounted, on retire l'écouteur pour éviter les fuites de mémoire. On stocke la référence du gestionnaire sur l'élément (el._clickOutside).
v-tooltip
Une simple tooltip affichée au survol de la souris :
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)
Détecter le moment où un élément apparaît dans le viewport - idéal pour le lazy loading et les animations d'entrée :
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}Utilisation :
1<template>
2 <div v-intersection.once="onVisible">
3 Apparaîtra quand l'élément sera visible
4 </div>
5</template>Nettoyage - la règle d'or des directives
Chaque écouteur, observer ou timer ajouté dans mounted DOIT être retiré dans unmounted. Sinon, des fuites de mémoire se produiront.
Pattern :
- Dans
mounted- ajoute le gestionnaire et stocke la référence surel - Dans
unmounted- retire le gestionnaire à l'aide de la référence stockée
Code de cette leçon : App.vue
1<script setup>
2import { ref } from 'vue'
3
4// v-click-outside
5const vClickOutside = {
6 mounted(el, binding) {
7 el._clickOutside = (event) => {
8 if (!el.contains(event.target)) {
9 binding.value(event)
10 }
11 }
12 document.addEventListener('click', el._clickOutside)
13 },
14 unmounted(el) {
15 document.removeEventListener('click', el._clickOutside)
16 }
17}
18
19// v-tooltip (simplifiée)
20const vTooltip = {
21 mounted(el, binding) {
22 el.style.position = 'relative'
23 el.style.cursor = 'pointer'
24 const tip = document.createElement('span')
25 tip.textContent = binding.value
26 tip.style.cssText =
27 'position:absolute;bottom:110%;left:50%;transform:translateX(-50%);' +
28 'background:#333;color:#00ff88;padding:4px 10px;border-radius:4px;' +
29 'font-size:12px;white-space:nowrap;opacity:0;transition:opacity 0.2s;' +
30 'pointer-events:none;z-index:100;'
31 el.appendChild(tip)
32 el._tipShow = () => { tip.style.opacity = '1' }
33 el._tipHide = () => { tip.style.opacity = '0' }
34 el.addEventListener('mouseenter', el._tipShow)
35 el.addEventListener('mouseleave', el._tipHide)
36 },
37 unmounted(el) {
38 el.removeEventListener('mouseenter', el._tipShow)
39 el.removeEventListener('mouseleave', el._tipHide)
40 }
41}
42
43const menuOpen = ref(false)
44const items = ref([
45 { id: 1, visible: false, label: 'Panneau de navigation' },
46 { id: 2, visible: false, label: 'Panneau de diagnostic' },
47 { id: 3, visible: false, label: 'Panneau de communication' }
48])
49</script>
50
51<template>
52 <div class="nova-lab">
53 <h1>Directives avancées</h1>
54
55 <section class="section">
56 <h2>v-click-outside</h2>
57 <div v-click-outside="() => menuOpen = false" class="menu-wrapper">
58 <button @click.stop="menuOpen = !menuOpen" class="btn">
59 {{ menuOpen ? 'Fermer le menu' : 'Ouvrir le menu' }}
60 </button>
61 <ul v-if="menuOpen" class="dropdown">
62 <li>Navigation</li>
63 <li>Diagnostic</li>
64 <li>Communication</li>
65 <li>Systèmes</li>
66 </ul>
67 </div>
68 <p class="hint">Clique en dehors du menu pour le fermer</p>
69 </section>
70
71 <section class="section">
72 <h2>v-tooltip</h2>
73 <div class="tooltip-row">
74 <button v-tooltip="'Ouvrir le panneau de navigation'" class="btn">NAV</button>
75 <button v-tooltip="'Diagnostic des systèmes'" class="btn cyan">DIAG</button>
76 <button v-tooltip="'Système de communication'" class="btn red">COM</button>
77 </div>
78 <p class="hint">Survole les boutons</p>
79 </section>
80
81 <section class="section">
82 <h2>Nettoyage - la règle d'or</h2>
83 <p>Chaque écouteur ajouté dans mounted() DOIT être retiré dans unmounted().</p>
84 <p>Pattern : sauvegarde une référence sur el (par ex. el._handler), retire-la dans unmounted.</p>
85 </section>
86 </div>
87</template>
88
89<style scoped>
90.nova-lab {
91 background: #0a0e27;
92 color: #c0c0c0;
93 min-height: 100vh;
94 padding: 20px;
95 font-family: monospace;
96}
97h1 { color: #00ff88; margin-bottom: 20px; }
98h2 { color: #00b4d8; font-size: 16px; margin-bottom: 10px; }
99.section {
100 background: rgba(0, 180, 216, 0.05);
101 border: 1px solid rgba(0, 180, 216, 0.2);
102 padding: 15px;
103 border-radius: 8px;
104 margin-bottom: 15px;
105}
106.btn {
107 background: rgba(0, 255, 136, 0.15);
108 color: #00ff88;
109 border: 1px solid #00ff88;
110 padding: 8px 16px;
111 border-radius: 6px;
112 cursor: pointer;
113 font-family: monospace;
114}
115.btn.cyan { color: #00b4d8; border-color: #00b4d8; background: rgba(0,180,216,0.15); }
116.btn.red { color: #e74c3c; border-color: #e74c3c; background: rgba(231,76,60,0.15); }
117.menu-wrapper { position: relative; display: inline-block; }
118.dropdown {
119 position: absolute; top: 100%; left: 0;
120 background: #1a1e37; border: 1px solid #00ff88;
121 border-radius: 6px; list-style: none;
122 padding: 0; margin: 4px 0; min-width: 160px; z-index: 10;
123}
124.dropdown li {
125 padding: 8px 16px; cursor: pointer;
126}
127.dropdown li:hover { background: rgba(0,255,136,0.1); }
128.tooltip-row { display: flex; gap: 12px; }
129.hint { color: #666; font-size: 12px; margin-top: 8px; }
130</style>Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Où l'écouteur d'événement doit-il être ajouté dans la directive v-click-outside ?
2. Pourquoi faut-il retirer les écouteurs d'événements dans le hook unmounted ?
Tâches pratiques dans le jeu
- Éditeur de code
Ajoute un écouteur sur document dans mounted, vérifie el.contains(event.target), retire-le dans unmounted.
- Tri vertical
Ordonne les étapes du pattern de nettoyage d'une directive dans le bon ordre :
- Clique dans l'ordre
Ordonne la condition qui vérifie si un clic s'est produit en dehors de l'élément :
- Éditeur de code
Crée une directive qui ajoute une tooltip à un élément au survol de la souris.