Cours JavaScript et TypeScript · Module 8: Événements et DOM
La propagation des événements (Event Bubbling)
Dans cette leçon7
« Regarde », Ray Arnold te conduit jusqu'au panneau d'alarme central de Jurassic Park. « Quand un capteur détecte une menace dans l'enclos 7-B, le signal ne s'arrête pas là. Il remonte - vers le système du secteur, puis vers le centre de contrôle principal, et enfin vers l'alarme centrale. C'est ce qu'on appelle la propagation du signal. »
Ray montre le schéma du système d'alarme. « Dans le DOM, ça fonctionne exactement de la même façon. Quand tu cliques sur un élément, l'événement "bouillonne" vers le haut à travers tout l'arbre du DOM - depuis l'élément cible, à travers ses parents, jusqu'au document lui-même. »
Qu'est-ce que le bouillonnement d'événements ?
Le bouillonnement d'événements (event bubbling) est un mécanisme fondamental en JavaScript où les événements se propagent depuis l'élément sur lequel ils se sont produits, vers le haut, à travers tous ses ancêtres dans l'arbre du DOM.
Les trois phases de la propagation d'un événement
« La plupart des événements », explique Ray, « passent par trois phases. C'est comme un protocole de sécurité - on vérifie tout dans un ordre précis. » Certains événements, comme focus, blur, mouseenter et mouseleave, ne bouillonnent pas : ils passent par la phase de capture et atteignent la cible, mais ne remontent pas. Les équivalents de focus et blur qui bouillonnent sont focusin et focusout.
1// Phases de l'événement :
2// 1. CAPTURE (capturing) - du document jusqu'à l'élément cible
3// 2. TARGET (cible) - sur l'élément cible lui-même
4// 3. BUBBLE (bubbling) - de l'élément cible jusqu'au document
5
6document.addEventListener('click', function(event) {
7 // eventPhase indique la phase courante :
8 // 1 = CAPTURING_PHASE
9 // 2 = AT_TARGET
10 // 3 = BUBBLING_PHASE
11 console.log('Phase de l\'événement :', event.eventPhase);
12}, true); // true = écouter en phase de captureExemple pratique de propagation
« Voyons cela en action », dit Ray en ouvrant dans le système la structure de l'enclos des dinosaures.
1// Structure HTML :
2// <div id="park-zone" class="zone">
3// <div id="sector-b" class="sector">
4// <div id="enclosure-7" class="enclosure">
5// <button id="emergency-btn" class="button">ALARME</button>
6// </div>
7// </div>
8// </div>
9
10const parkZone = document.getElementById('park-zone');
11const sectorB = document.getElementById('sector-b');
12const enclosure7 = document.getElementById('enclosure-7');
13const emergencyBtn = document.getElementById('emergency-btn');
14
15// Écouteur sur le bouton (le plus imbriqué)
16emergencyBtn.addEventListener('click', function(event) {
17 console.log('1. ALARME ! Bouton d\'urgence pressé');
18 console.log(' Target :', event.target.id);
19 console.log(' CurrentTarget :', event.currentTarget.id);
20 console.log(' Phase :', event.eventPhase); // 2 (AT_TARGET)
21
22 activateLocalAlarm();
23});
24
25// Écouteur sur l'enclos
26enclosure7.addEventListener('click', function(event) {
27 console.log('2. Enclos 7 - activation d\'alarme détectée');
28 console.log(' Target :', event.target.id); // emergency-btn
29 console.log(' CurrentTarget :', event.currentTarget.id); // enclosure-7
30 console.log(' Phase :', event.eventPhase); // 3 (BUBBLING_PHASE)
31
32 closeEnclosureGates();
33});
34
35// Écouteur sur le secteur
36sectorB.addEventListener('click', function(event) {
37 console.log('3. Secteur B - alarme propagée au secteur');
38 console.log(' Target :', event.target.id); // emergency-btn
39 console.log(' CurrentTarget :', event.currentTarget.id); // sector-b
40 console.log(' Phase :', event.eventPhase); // 3 (BUBBLING_PHASE)
41
42 lockSectorGates();
43});
44
45// Écouteur sur toute la zone du parc
46parkZone.addEventListener('click', function(event) {
47 console.log('4. Zone du parc - l\'alarme a atteint le contrôle central');
48 console.log(' Target :', event.target.id); // emergency-btn
49 console.log(' CurrentTarget :', event.currentTarget.id); // park-zone
50 console.log(' Phase :', event.eventPhase); // 3 (BUBBLING_PHASE)
51
52 notifyCentralControl();
53});
54
55// Écouteur sur tout le document
56document.addEventListener('click', function(event) {
57 console.log('5. DOCUMENT - le plus haut niveau du système notifié');
58 console.log(' L\'événement a atteint le sommet de la hiérarchie');
59
60 logSystemEvent(event);
61});
62
63function activateLocalAlarm() {
64 console.log(' → Activation de l\'alarme locale dans l\'enclos 7');
65}
66
67function closeEnclosureGates() {
68 console.log(' → Fermeture des portails de l\'enclos 7');
69}
70
71function lockSectorGates() {
72 console.log(' → Verrouillage de tous les portails du secteur B');
73}
74
75function notifyCentralControl() {
76 console.log(' → Notification du centre de contrôle central');
77}
78
79function logSystemEvent(event) {
80 console.log(' → Enregistrement de l\'événement dans le système principal');
81}
82
83// Après avoir cliqué sur le bouton, on verra les 5 messages dans l'ordre !stopPropagation() - arrêter la propagation
« Parfois », Ray appuie sur un bouton rouge, « on veut arrêter la propagation. Comme une vanne de sécurité qui isole le problème localement. »
Arrêt contrôlé
1const button = document.getElementById('local-action-btn');
2const container = document.getElementById('action-container');
3const section = document.getElementById('main-section');
4
5button.addEventListener('click', function(event) {
6 console.log('1. Bouton cliqué');
7 performLocalAction();
8
9 // ARRÊTER la propagation - l'événement ne remontera pas
10 event.stopPropagation();
11 console.log(' Propagation arrêtée - l\'événement n\'atteindra pas les parents');
12});
13
14container.addEventListener('click', function(event) {
15 console.log('2. Cet écouteur NE S\'EXÉCUTERA PAS (à cause de stopPropagation)');
16 performContainerAction();
17});
18
19section.addEventListener('click', function(event) {
20 console.log('3. Cet écouteur non plus NE S\'EXÉCUTERA PAS');
21 performSectionAction();
22});
23
24// Après avoir cliqué sur le bouton, on ne verra que le message « 1. »Cas d'usage pratique - fenêtre modale
« Le cas le plus fréquent », explique Ray, « c'est la fermeture des fenêtres modales. »
1// Modale qui se ferme quand on clique sur l'arrière-plan (overlay)
2const modal = document.getElementById('dino-info-modal');
3const modalContent = document.getElementById('modal-content');
4const closeButton = document.getElementById('modal-close');
5
6// Cliquer sur l'arrière-plan (overlay) ferme la modale
7modal.addEventListener('click', function(event) {
8 console.log('Arrière-plan cliqué - fermeture de la modale');
9 closeModal();
10});
11
12// Cliquer sur le contenu de la modale NE la ferme PAS
13modalContent.addEventListener('click', function(event) {
14 event.stopPropagation(); // IMPORTANT ! Arrêter la propagation vers l'overlay
15 console.log('Contenu cliqué - la modale reste ouverte');
16});
17
18// Bouton de fermeture
19closeButton.addEventListener('click', function(event) {
20 event.stopPropagation(); // Facultatif, mais plus sûr
21 console.log('Bouton de fermeture - fermeture de la modale');
22 closeModal();
23});
24
25function closeModal() {
26 modal.style.display = 'none';
27 console.log('Modale fermée');
28}
29
30function openModal(dinosaurId) {
31 modal.style.display = 'block';
32 loadDinosaurData(dinosaurId);
33 console.log('Modale ouverte pour le dinosaure :', dinosaurId);
34}La phase de capture
« La plupart des développeurs », dit Ray, « n'utilisent que la phase de bouillonnement. Mais parfois, on a besoin de la phase de capture - vérifier tout AVANT que l'événement n'atteigne la cible. »
Écouter dans la phase de capture
1const outer = document.getElementById('outer');
2const middle = document.getElementById('middle');
3const inner = document.getElementById('inner');
4
5// Phase de bouillonnement (par défaut, capture: false)
6outer.addEventListener('click', function(event) {
7 console.log('BUBBLE : Outer');
8});
9
10middle.addEventListener('click', function(event) {
11 console.log('BUBBLE : Middle');
12});
13
14inner.addEventListener('click', function(event) {
15 console.log('BUBBLE : Inner (cible)');
16});
17
18// Phase de capture (capture: true)
19outer.addEventListener('click', function(event) {
20 console.log('CAPTURE : Outer');
21}, true); // true active la phase de capture
22
23middle.addEventListener('click', function(event) {
24 console.log('CAPTURE : Middle');
25}, true);
26
27inner.addEventListener('click', function(event) {
28 console.log('CAPTURE : Inner (cible)');
29}, true);
30
31// Après avoir cliqué sur inner, on verra :
32// CAPTURE : Outer (descente)
33// CAPTURE : Middle (descente)
34// CAPTURE : Inner (cible) (cible)
35// BUBBLE : Inner (cible) (cible)
36// BUBBLE : Middle (remontée)
37// BUBBLE : Outer (remontée)Utilisation pratique de la capture - interception globale
1// Gestionnaire global pour tous les clics (ex : pour les analytics)
2document.addEventListener('click', function(event) {
3 const targetElement = event.target;
4
5 console.log('=== TRACKER DE CLICS GLOBAL ===');
6 console.log('Élément :', targetElement.tagName);
7 console.log('ID :', targetElement.id);
8 console.log('Classes :', targetElement.className);
9 console.log('Position :', event.clientX, event.clientY);
10 console.log('Heure :', new Date().toISOString());
11
12 // Envoyer au système d'analytics
13 trackClickEvent({
14 element: targetElement.tagName,
15 elementId: targetElement.id,
16 elementClass: targetElement.className,
17 timestamp: Date.now(),
18 position: { x: event.clientX, y: event.clientY }
19 });
20}, true); // Capture - s'exécute AVANT tous les autres gestionnaires
21
22function trackClickEvent(data) {
23 console.log('Envoi des données analytics :', data);
24 // Envoyer au serveur d'analytics
25}stopImmediatePropagation() - arrêt immédiat
« C'est une mesure plus radicale », prévient Ray. « Elle arrête NON SEULEMENT la propagation vers les parents, mais aussi tous les écouteurs restants sur le même élément. »
1const criticalButton = document.getElementById('critical-system-btn');
2
3// Premier écouteur
4criticalButton.addEventListener('click', function(event) {
5 console.log('1. Premier écouteur - s\'exécute');
6
7 const systemStatus = checkSystemStatus();
8
9 if (systemStatus === 'CRITICAL') {
10 console.log(' SYSTÈME CRITIQUE - arrêt de toutes les actions restantes !');
11 event.stopImmediatePropagation(); // TOUT ARRÊTER !
12 handleCriticalSituation();
13 return;
14 }
15});
16
17// Deuxième écouteur - NE S'EXÉCUTERA PAS si stopImmediatePropagation a été appelé
18criticalButton.addEventListener('click', function(event) {
19 console.log('2. Deuxième écouteur - flux normal');
20 performNormalAction();
21});
22
23// Troisième écouteur - NE S'EXÉCUTERA PAS si stopImmediatePropagation a été appelé
24criticalButton.addEventListener('click', function(event) {
25 console.log('3. Troisième écouteur - journalisation');
26 logAction();
27});
28
29function checkSystemStatus() {
30 // Simulation d'une vérification du statut du système
31 const random = Math.random();
32 return random > 0.7 ? 'CRITICAL' : 'NORMAL';
33}
34
35function handleCriticalSituation() {
36 console.log(' → Activation des protocoles d\'urgence');
37 console.log(' → Verrouillage de tous les systèmes');
38 console.log(' → Notification de l\'administration');
39}Système de notification pratique
« Voyons comment utiliser la propagation dans un vrai système », dit Ray en ouvrant le code du système de notifications du parc.
1class ParkNotificationSystem {
2 constructor() {
3 this.notifications = [];
4 this.setupGlobalListeners();
5 }
6
7 setupGlobalListeners() {
8 // Écouteur global en phase de capture
9 document.addEventListener('click', (e) => {
10 this.trackInteraction(e);
11 }, true);
12
13 // Écouteur pour toutes les notifications
14 const notificationContainer = document.getElementById('notifications');
15
16 if (notificationContainer) {
17 notificationContainer.addEventListener('click', (e) => {
18 this.handleNotificationClick(e);
19 });
20 }
21 }
22
23 trackInteraction(event) {
24 const target = event.target;
25
26 // Ne suivre que les interactions clés
27 if (target.matches('.trackable')) {
28 console.log('Interaction avec un élément suivi :', target.id);
29
30 this.logInteraction({
31 element: target.id,
32 type: event.type,
33 timestamp: Date.now()
34 });
35 }
36 }
37
38 handleNotificationClick(event) {
39 const notification = event.target.closest('.notification');
40
41 if (!notification) return;
42
43 // Arrêter la propagation - on ne veut pas que le clic sur la notification
44 // déclenche d'autres gestionnaires
45 event.stopPropagation();
46
47 const notificationId = notification.dataset.id;
48 const action = event.target.dataset.action;
49
50 console.log(`Notification cliquée : ${notificationId}`);
51 console.log(`Action : ${action}`);
52
53 switch(action) {
54 case 'dismiss':
55 this.dismissNotification(notificationId);
56 break;
57
58 case 'view':
59 this.viewNotification(notificationId);
60 break;
61
62 case 'action':
63 this.performNotificationAction(notificationId);
64 break;
65
66 default:
67 console.log('Cliqué quelque part dans la notification - affichage des détails');
68 this.showNotificationDetails(notificationId);
69 }
70 }
71
72 dismissNotification(id) {
73 console.log(`Fermeture de la notification : ${id}`);
74 const notification = document.querySelector(`[data-id="${id}"]`);
75
76 if (notification) {
77 notification.classList.add('dismissing');
78
79 setTimeout(() => {
80 notification.remove();
81 this.notifications = this.notifications.filter(n => n.id !== id);
82 console.log(`Notification ${id} supprimée`);
83 }, 300);
84 }
85 }
86
87 viewNotification(id) {
88 console.log(`Affichage des détails de la notification : ${id}`);
89 // Logique d'affichage des détails
90 }
91
92 performNotificationAction(id) {
93 const notification = this.notifications.find(n => n.id === id);
94
95 if (notification && notification.actionCallback) {
96 console.log(`Exécution de l'action pour la notification : ${id}`);
97 notification.actionCallback();
98 }
99 }
100
101 showNotificationDetails(id) {
102 console.log(`Affichage des détails complets de la notification : ${id}`);
103 // Logique d'affichage des détails complets
104 }
105
106 logInteraction(data) {
107 console.log('Journalisation de l\'interaction :', data);
108 // Sauvegarder dans le système d'analytics
109 }
110
111 createNotification(message, type = 'info', actionCallback = null) {
112 const id = 'notif-' + Date.now();
113
114 const notification = {
115 id,
116 message,
117 type,
118 actionCallback,
119 timestamp: new Date()
120 };
121
122 this.notifications.push(notification);
123 this.renderNotification(notification);
124
125 console.log('Nouvelle notification créée :', id);
126 return id;
127 }
128
129 renderNotification(notification) {
130 const container = document.getElementById('notifications');
131
132 const notifElement = document.createElement('div');
133 notifElement.className = `notification notification-${notification.type}`;
134 notifElement.dataset.id = notification.id;
135
136 notifElement.innerHTML = `
137 <div class="notification-content">
138 <p>${notification.message}</p>
139 <small>${notification.timestamp.toLocaleTimeString()}</small>
140 </div>
141 <div class="notification-actions">
142 ${notification.actionCallback ? '<button data-action="action">Action</button>' : ''}
143 <button data-action="dismiss">Fermer</button>
144 </div>
145 `;
146
147 container.appendChild(notifElement);
148 }
149}
150
151// Initialisation du système
152const parkNotifications = new ParkNotificationSystem();
153
154// Exemple d'utilisation
155parkNotifications.createNotification(
156 'Activité inhabituelle détectée dans l\'enclos du T-Rex',
157 'warning',
158 () => {
159 console.log('Action : ouverture du panneau de surveillance du T-Rex');
160 openTrexMonitoring();
161 }
162);Récapitulatif
« La propagation des événements », résume Ray, « est un mécanisme puissant. Tout comme dans les systèmes d'alarme du parc, où un signal se propage vers les niveaux appropriés, les événements en JavaScript circulent à travers l'arbre du DOM. Comprendre ce mécanisme est crucial pour une gestion efficace des événements. »
Concepts clés :
- La plupart des événements "bouillonnent" depuis l'élément cible jusqu'au
documentet àwindow(focus,blur,mouseenteretmouseleavene bouillonnent pas) - Trois phases : Capture → Cible → Bouillonnement
event.stopPropagation()arrête la propagation vers les parentsevent.stopImmediatePropagation()arrête tous les écouteurs restants- La phase de capture (troisième paramètre
true) s'exécute avant le bouillonnement event.targetvsevent.currentTarget- différence cruciale pendant la propagation
Code de cette leçon : index.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Modale dinosaure - Parc Jurassique</title>
7 <style>
8 body {
9 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10 background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
11 margin: 0;
12 padding: 20px;
13 color: white;
14 }
15
16 .main-container {
17 max-width: 800px;
18 margin: 0 auto;
19 }
20
21 h1 {
22 text-align: center;
23 margin-bottom: 30px;
24 }
25
26 .dino-buttons {
27 display: grid;
28 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
29 gap: 20px;
30 margin-bottom: 30px;
31 }
32
33 .dino-button {
34 background: linear-gradient(45deg, #3498db, #2980b9);
35 color: white;
36 border: none;
37 border-radius: 10px;
38 padding: 20px;
39 cursor: pointer;
40 transition: all 0.3s ease;
41 font-size: 16px;
42 font-weight: bold;
43 }
44
45 .dino-button:hover {
46 transform: translateY(-5px);
47 box-shadow: 0 10px 25px rgba(52, 152, 219, 0.4);
48 }
49
50 .dino-emoji {
51 font-size: 48px;
52 display: block;
53 margin-bottom: 10px;
54 }
55
56 /* Styles pour la modale */
57 .modal {
58 display: none;
59 position: fixed;
60 z-index: 1000;
61 left: 0;
62 top: 0;
63 width: 100%;
64 height: 100%;
65 background: rgba(0, 0, 0, 0.8);
66 backdrop-filter: blur(5px);
67 opacity: 0;
68 transition: opacity 0.3s ease;
69 }
70
71 .modal.show {
72 opacity: 1;
73 }
74
75 .modal-content {
76 background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
77 margin: 5% auto;
78 padding: 0;
79 border-radius: 15px;
80 width: 90%;
81 max-width: 600px;
82 max-height: 80vh;
83 overflow-y: auto;
84 transform: scale(0.7);
85 transition: transform 0.3s ease;
86 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
87 }
88
89 .modal.show .modal-content {
90 transform: scale(1);
91 }
92
93 .modal-header {
94 background: linear-gradient(45deg, #e74c3c, #c0392b);
95 padding: 20px;
96 color: white;
97 display: flex;
98 justify-content: space-between;
99 align-items: center;
100 }
101
102 .modal-header h2 {
103 margin: 0;
104 font-size: 24px;
105 }
106
107 .close-btn {
108 background: none;
109 border: none;
110 color: white;
111 font-size: 24px;
112 cursor: pointer;
113 padding: 0;
114 width: 30px;
115 height: 30px;
116 border-radius: 50%;
117 display: flex;
118 align-items: center;
119 justify-content: center;
120 transition: all 0.3s ease;
121 }
122
123 .close-btn:hover {
124 background: rgba(255, 255, 255, 0.2);
125 transform: rotate(90deg);
126 }
127
128 .modal-body {
129 padding: 30px;
130 color: white;
131 line-height: 1.6;
132 }
133
134 .dino-image {
135 width: 100%;
136 max-width: 300px;
137 height: 200px;
138 background: rgba(255, 255, 255, 0.1);
139 border-radius: 10px;
140 margin: 20px auto;
141 display: flex;
142 align-items: center;
143 justify-content: center;
144 font-size: 80px;
145 }
146
147 .dino-stats {
148 display: grid;
149 grid-template-columns: 1fr 1fr;
150 gap: 15px;
151 margin: 20px 0;
152 }
153
154 .stat-item {
155 background: rgba(255, 255, 255, 0.1);
156 padding: 15px;
157 border-radius: 8px;
158 text-align: center;
159 }
160
161 .stat-label {
162 color: #bdc3c7;
163 font-size: 14px;
164 margin-bottom: 5px;
165 }
166
167 .stat-value {
168 font-size: 18px;
169 font-weight: bold;
170 color: #ecf0f1;
171 }
172
173 .modal-footer {
174 padding: 20px 30px;
175 border-top: 1px solid rgba(255, 255, 255, 0.1);
176 display: flex;
177 gap: 15px;
178 justify-content: center;
179 }
180
181 .action-btn {
182 padding: 12px 24px;
183 border: none;
184 border-radius: 8px;
185 cursor: pointer;
186 font-weight: bold;
187 transition: all 0.3s ease;
188 }
189
190 .book-tour-btn {
191 background: #27ae60;
192 color: white;
193 }
194
195 .book-tour-btn:hover {
196 background: #2ecc71;
197 }
198
199 .add-favorite-btn {
200 background: #f39c12;
201 color: white;
202 }
203
204 .add-favorite-btn:hover {
205 background: #e67e22;
206 }
207
208 .instructions {
209 background: rgba(52, 152, 219, 0.2);
210 border: 1px solid #3498db;
211 border-radius: 10px;
212 padding: 20px;
213 margin-bottom: 30px;
214 }
215
216 .key-hint {
217 font-size: 14px;
218 color: #bdc3c7;
219 text-align: center;
220 margin-top: 20px;
221 }
222
223 .key-hint kbd {
224 background: rgba(255, 255, 255, 0.2);
225 padding: 2px 8px;
226 border-radius: 4px;
227 font-family: monospace;
228 }
229 </style>
230</head>
231<body>
232 <div class="main-container">
233 <h1>Informations sur les dinosaures</h1>
234
235 <div class="instructions">
236 Click on a dinosaur button to see detailed information in a modal window.
237 <div class="key-hint">
238 You can close the modal by clicking <kbd>×</kbd>, clicking outside the window, or pressing <kbd>Escape</kbd>
239 </div>
240 </div>
241
242 <div class="dino-buttons">
243 <button class="dino-button" data-dino="trex">
244 <span class="dino-emoji">◆</span>
245 Tyrannosaurus Rex
246 </button>
247
248 <button class="dino-button" data-dino="velociraptor">
249 <span class="dino-emoji">◆</span>
250 Velociraptor
251 </button>
252
253 <button class="dino-button" data-dino="triceratops">
254 <span class="dino-emoji">◆</span>
255 Triceratops
256 </button>
257
258 <button class="dino-button" data-dino="brachiosaurus">
259 <span class="dino-emoji">◆</span>
260 Brachiosaurus
261 </button>
262 </div>
263 </div>
264
265 <!-- Modal (initially hidden) -->
266 <div id="dinosaur-modal" class="modal">
267 <div class="modal-content">
268 <div class="modal-header">
269 <h2 id="modal-title">Informations sur le dinosaure</h2>
270 <button class="close-btn" id="close-modal">×</button>
271 </div>
272 <div class="modal-body">
273 <div id="dino-image" class="dino-image">◆</div>
274
275 <div id="dino-description">
276 Dinosaur description will be loaded here...
277 </div>
278
279 <div class="dino-stats">
280 <div class="stat-item">
281 <div class="stat-label">Ère géologique</div>
282 <div class="stat-value" id="era-value">-</div>
283 </div>
284 <div class="stat-item">
285 <div class="stat-label">Régime</div>
286 <div class="stat-value" id="diet-value">-</div>
287 </div>
288 <div class="stat-item">
289 <div class="stat-label">Length</div>
290 <div class="stat-value" id="length-value">-</div>
291 </div>
292 <div class="stat-item">
293 <div class="stat-label">Poids</div>
294 <div class="stat-value" id="weight-value">-</div>
295 </div>
296 </div>
297 </div>
298 <div class="modal-footer">
299 <button class="action-btn book-tour-btn" id="book-tour">
300 Book a tour
301 </button>
302 <button class="action-btn add-favorite-btn" id="add-favorite">
303 Ajouter aux favoris
304 </button>
305 </div>
306 </div>
307 </div>
308
309 <script>
310 // Données sur les dinosaurs
311 const dinosaurData = {
312 trex: {
313 name: "Tyrannosaurus Rex",
314 emoji: "",
315 era: "Late Cretaceous",
316 diet: "Carnivore",
317 length: "12-13 m",
318 weight: "8-9 ton",
319 description: "Tyrannosaurus Rex, known as \"King of tyrant lizards\", was one of the largest land predators of all time. Its powerful jaws could exert a force of up to 12,800 pounds per square inch. T-Rex lived approximately 68-66 million years ago in what is now North America."
320 },
321 velociraptor: {
322 name: "Velociraptor",
323 emoji: "",
324 era: "Late Cretaceous",
325 diet: "Carnivore",
326 length: "2 m",
327 weight: "15-20 kg",
328 description: "Velociraptor was an intelligent, fast pack-hunting predator. Its name means \"swift hunter\". It was characterized by sickle-shaped claws on its hind legs, which were used to deliver lethal wounds to prey. It lived approximately 75-71 million years ago in Asia."
329 },
330 triceratops: {
331 name: "Triceratops",
332 emoji: "",
333 era: "Late Cretaceous",
334 diet: "Herbivore",
335 length: "8-9 m",
336 weight: "6-12 ton",
337 description: "Triceratops was one of the last dinosaurs before the great extinction. Its characteristic three horns and bony frill servaient à se défendre contre les prédateurs et à rivaliser avec d'autres membres de son espèce. Il vivait il y a environ 68 à 66 millions d'années en Amérique du Nord."
338 },
339 brachiosaurus: {
340 name: "Brachiosaurus",
341 emoji: "",
342 era: "Late Jurassic",
343 diet: "Herbivore",
344 length: "20-23 m",
345 weight: "35-40 ton",
346 description: "Brachiosaurus was one of the largest dinosaurs that ever walked the Earth. Its long neck allowed it to reach the tops of the tallest trees. Contrairement aux autres sauropodes, ses pattes avant étaient plus longues que ses pattes arrière. Il vivait il y a environ 156 à 145 millions d'années."
347 }
348 };
349
350 // Get references to elements
351 const modal = document.getElementById('dinosaur-modal');
352 const closeBtn = document.getElementById('close-modal');
353 const modalTitle = document.getElementById('modal-title');
354 const dinoImage = document.getElementById('dino-image');
355 const dinoDescription = document.getElementById('dino-description');
356 const eraValue = document.getElementById('era-value');
357 const dietValue = document.getElementById('diet-value');
358 const lengthValue = document.getElementById('length-value');
359 const weightValue = document.getElementById('weight-value');
360 const bookTourBtn = document.getElementById('book-tour');
361 const addFavoriteBtn = document.getElementById('add-favorite');
362
363 // Fonction d'ouverture de la modale
364 function openModal(dinoType) {
365 const dino = dinosaurData[dinoType];
366
367 // Fill modal with dinosaur data
368 modalTitle.textContent = dino.name;
369 dinoImage.textContent = dino.emoji;
370 dinoDescription.textContent = dino.description;
371 eraValue.textContent = dino.era;
372 dietValue.textContent = dino.diet;
373 lengthValue.textContent = dino.length;
374 weightValue.textContent = dino.weight;
375
376 // Show modal with animation
377 modal.style.display = 'block';
378 requestAnimationFrame(() => {
379 modal.classList.add('show');
380 });
381
382 // Blocage du scroll sur le body
383 document.body.style.overflow = 'hidden';
384
385 console.log(`Modale ouverte pour: ${dino.name}`);
386 }
387
388 // Fonction de fermeture de la modale
389 function closeModal() {
390 modal.classList.remove('show');
391
392 setTimeout(() => {
393 modal.style.display = 'none';
394 document.body.style.overflow = 'auto';
395 }, 300);
396
397 console.log('Modal closed');
398 }
399
400 // Event listeners for dinosaur buttons
401 document.querySelectorAll('.dino-button').forEach(button => {
402 button.addEventListener('click', function() {
403 const dinoType = this.dataset.dino;
404 openModal(dinoType);
405 });
406 });
407
408 // Event listener pour le bouton de fermeture
409 closeBtn.addEventListener('click', closeModal);
410
411 // Close modal when clicking outside content
412 modal.addEventListener('click', function(event) {
413 if (event.target === modal) {
414 closeModal();
415 }
416 });
417
418 // Handle Escape key
419 document.addEventListener('keydown', function(event) {
420 if (event.key === 'Escape' && modal.classList.contains('show')) {
421 closeModal();
422 }
423 });
424
425 // Event listeners pour les actions dans la modale
426 bookTourBtn.addEventListener('click', function() {
427 const dinoName = modalTitle.textContent;
428 alert(`Tour to ${dinoName} has been booked for the nearest available date!\n\nDetails will be sent to your email.`);
429 closeModal();
430 });
431
432 addFavoriteBtn.addEventListener('click', function() {
433 const dinoName = modalTitle.textContent;
434
435 if (this.textContent.includes('Ajouté')) {
436 this.textContent = 'Ajouter aux favoris';
437 this.style.background = '#f39c12';
438 alert(`Removed ${dinoName} from favorites.`);
439 } else {
440 this.textContent = 'Ajouté aux favoris';
441 this.style.background = '#27ae60';
442 alert(`${dinoName} has been added to your favorite dinosaurs!`);
443 }
444 });
445
446 // Démonstration de l'ouverture programmatique de la modale
447 setTimeout(() => {
448 console.log('Demo: Ouverture automatique de la modale T-Rex dans 3 secondes...');
449 }, 3000);
450
451 // Optional: open modal automatically after 5 seconds
452 // setTimeout(() => openModal('trex'), 5000);
453 </script>
454</body>
455</html>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. Comment appelle-t-on le processus durant lequel un événement remonte depuis l'élément cible à travers ses parents ?
2. Dans quelle phase du flux d'événement opère un écouteur avec l'option { capture: true } ?
Tâches pratiques dans le jeu
- Éditeur de code
Le fichier index.html contient un bouton #open-modal et une fenêtre modale cachée #dino-modal (style.css ne l'affiche qu'avec la classe open). Complète les blancs du script : ___BLANK1___ est la méthode de classList qui ajoute la classe open au clic sur le bouton, ___BLANK2___ est l'événement d'appui sur une touche écouté sur le document, et ___BLANK3___ est la valeur de event.key pour la touche Échap. La fenêtre doit s'ouvrir au clic et se fermer quand on appuie sur Échap, mais pas avec une autre touche.
- Tri vertical
Dans quel ordre se déroule le flux d'événement dans le DOM ?
- Tri vertical
Place dans l'ordre les étapes pour arrêter la propagation d'un événement :