JavaScript : déplacement de la souris — mouseover/out, mouseenter/leave
Découvrez les différences entre mouseover/mouseout et mouseenter/mouseleave en JavaScript : propagation, relatedTarget, le piège des éléments enfants et des exemples interactifs.
Comprendre les événements de déplacement de la souris en JavaScript : Mouseover, Mouseout, Mouseenter et Mouseleave
Les événements de déplacement de la souris en JavaScript permettent aux développeurs de réagir aux mouvements du curseur sur les éléments d'une page web. Ces événements sont essentiels pour créer des interfaces interactives et réactives qui répondent aux actions de l'utilisateur. Ce guide explore les différences entre les événements mouseover, mouseout, mouseenter et mouseleave, avec des exemples pratiques pour illustrer leur utilisation.
Les deux paires en un coup d'œil
JavaScript vous propose deux paires d'événements pour la même action physique — le pointeur qui entre sur un élément ou qui en sort. Elles semblent interchangeables, mais se comportent très différemment vis-à-vis des éléments enfants. Choisir la mauvaise paire est l'une des erreurs d'interface les plus fréquentes.
| Événement | Se déclenche quand le pointeur… | Se propage ? | Se redéclenche sur les éléments enfants ? |
|---|---|---|---|
mouseover | entre dans l'élément ou l'un de ses descendants | Oui | Oui |
mouseout | quitte l'élément ou l'un de ses descendants | Oui | Oui |
mouseenter | franchit la limite propre de l'élément | Non | Non |
mouseleave | quitte la limite propre de l'élément | Non | Non |
Mouseover et Mouseout
mouseover: se déclenche quand la souris entre dans l'élément ou dans l'un de ses enfants. Comme il se propage, c'est le bon choix pour la délégation d'événements — un seul écouteur sur un conteneur peut gérer le survol de nombreux éléments enfants.mouseout: le miroir demouseover— se déclenche quand la souris quitte l'élément ou l'un de ses enfants.
Le piège est que déplacer le pointeur d'un parent vers un enfant déclenche mouseout sur le parent (le pointeur a « quitté » la zone de texte du parent) immédiatement suivi de mouseover (il est « entré » dans l'enfant, ce qui compte toujours comme le parent). Ainsi, un simple survol visuel peut produire un flux d'événements parasite sur un parent ayant des enfants.
Mouseenter et Mouseleave
mouseenter: similaire àmouseover, mais ne se propage pas et ne se redéclenche pas quand le pointeur passe sur un élément enfant. Il se déclenche exactement une fois lorsque le pointeur entre pour la première fois dans la limite de l'élément — idéal pour le comportement « mettre en surbrillance cette carte au survol ».mouseleave: se déclenche une seule fois lorsque le pointeur quitte la limite extérieure de l'élément, sans tenir compte des déplacements entre descendants.
Règle générale : privilégiez
mouseenter/mouseleavequand vous voulez une logique claire « le pointeur est-il sur cet élément ? », etmouseover/mouseoutuniquement lorsque vous avez besoin de la propagation pour la délégation.
La propriété relatedTarget
Ces deux événements exposent event.relatedTarget, qui indique l'autre élément impliqué dans la transition :
- Avec
mouseover/mouseenter,relatedTargetest l'élément d'où vient le pointeur. - Avec
mouseout/mouseleave,relatedTargetest l'élément vers lequel se dirige le pointeur.
C'est ainsi que l'on reproduit le comportement de mouseenter tout en continuant à utiliser les événements mouseover/mouseout avec propagation : il suffit de vérifier si relatedTarget se trouve à l'intérieur de l'élément courant et d'ignorer l'événement si c'est le cas.
element.addEventListener('mouseout', function (event) {
// Ignore transitions to a descendant — only react to truly leaving.
if (this.contains(event.relatedTarget)) return;
console.log('Pointer really left the element');
});Notez que relatedTarget peut être null — par exemple lorsque le pointeur vient de l'extérieur de la fenêtre du navigateur — donc protégez-vous avant d'appeler contains().
Un piège : le « déplacement rapide de la souris »
mouseover/mouseout ne sont pas garantis de se déclencher pour chaque élément que le pointeur survole. Si l'utilisateur déplace la souris très rapidement, des éléments intermédiaires peuvent être complètement ignorés, et vous pouvez recevoir un mouseout pour un élément sans mouseover correspondant pour le suivant. Le code qui associe ces deux événements doit tolérer l'absence de partenaire. mouseenter/mouseleave sont toujours équilibrés pour l'élément auquel ils sont attachés, ce qui est une raison supplémentaire de les préférer pour le suivi d'état.
Exemples pratiques d'événements de déplacement de la souris
Ces exemples montrent comment implémenter des événements de déplacement de la souris pour améliorer l'expérience utilisateur grâce à des éléments interactifs.
Exemple 1 : Utiliser Mouseover et Mouseout
Cet exemple montre comment changer la couleur de fond d'une boîte lorsque le curseur de la souris y entre et en sort, y compris pour ses éléments enfants.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mouseover and Mouseout Example</title>
<style>
#box {
width: 200px;
height: 200px;
background-color: lightblue;
}
#innerBox {
width: 100px;
height: 100px;
background-color: lightcoral;
margin: 50px;
}
</style>
</head>
<body>
<div id="box">
Hover over me!
<div id="innerBox"></div>
</div>
<script>
document.getElementById('box').addEventListener('mouseover', function() {
this.style.backgroundColor = 'cyan';
});
document.getElementById('box').addEventListener('mouseout', function() {
this.style.backgroundColor = 'lightblue';
});
</script>
</body>
</html>Explication :
- L'événement
mouseoverchange la couleur de fond de la boîte en cyan, y compris lorsqu'on survole la boîte intérieure. - L'événement
mouseoutréinitialise la couleur de fond quand la souris quitte la boîte, en tenant compte là aussi de la boîte intérieure.
Exemple 2 : Implémenter Mouseenter et Mouseleave
Cet exemple améliore l'interaction utilisateur en montrant comment utiliser mouseenter et mouseleave pour une réaction plus précise, sans affecter les éléments enfants.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mouseenter and Mouseleave Visual Example</title>
<style>
#parent {
width: 400px;
height: 300px;
background-color: lightblue; /* Initial background color */
padding: 20px;
box-sizing: border-box;
position: relative;
display: flex;
justify-content: space-around;
align-items: center;
transition: background-color 0.3s ease;
}
.child {
width: 90px;
height: 90px;
background-color: lightsalmon;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
}
#feedback {
position: fixed;
bottom: 10px;
left: 10px;
background: white;
padding: 10px;
border: 1px solid #ccc;
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<div id="parent">
Parent Element
<div class="child">Child 1</div>
<div class="child">Child 2</div>
<div class="child">Child 3</div>
</div>
<div id="feedback">Hover over elements to see interactions.</div>
<script>
const parent = document.getElementById('parent');
const children = document.querySelectorAll('.child');
const feedback = document.getElementById('feedback');
parent.addEventListener('mouseenter', function() {
parent.style.backgroundColor = 'cyan'; // Highlight the parent on mouse enter
feedback.textContent = 'Mouse entered the parent element';
});
parent.addEventListener('mouseleave', function() {
parent.style.backgroundColor = 'lightblue'; // Revert color on mouse leave
feedback.textContent = 'Mouse left the parent element';
});
// Update feedback for child interactions
children.forEach(child => {
child.addEventListener('mouseenter', function() {
feedback.textContent = `Mouse entered ${this.textContent}`;
this.style.backgroundColor = '#ffcccb'; // Highlight child on mouse enter
});
child.addEventListener('mouseleave', function() {
feedback.textContent = `Mouse left ${this.textContent}`;
this.style.backgroundColor = 'lightsalmon'; // Revert child color on mouse leave
});
});
</script>
</body>
</html>Cet exemple démontre clairement comment les événements mouseenter et mouseleave se déclenchent de manière spécifique et ne se propagent pas, permettant des interactions distinctes et isolées avec les éléments imbriqués.
Exemple 3 : Simuler mouseleave avec relatedTarget
Parfois, vous avez besoin de la propagation (pour utiliser un seul écouteur délégué) et du comportement propre « uniquement quand le pointeur quitte vraiment ». Vous pouvez les combiner en écoutant le mouseout avec propagation et en ignorant les transitions vers des descendants grâce à relatedTarget. La logique est la même que celle vue plus haut, exprimée sous forme d'un petit assistant réutilisable :
function reallyLeft(event, element) {
// True only when the pointer moves to something OUTSIDE `element`.
const to = event.relatedTarget;
return to === null || !element.contains(to);
}
// Demonstrate without a browser: simulate a mouseout whose related
// target is a child (should be ignored) and one to an outside node.
const card = { contains: (node) => node === 'child' };
console.log(reallyLeft({ relatedTarget: 'child' }, card)); // false (still inside)
console.log(reallyLeft({ relatedTarget: 'outside' }, card)); // true (really left)
console.log(reallyLeft({ relatedTarget: null }, card)); // true (left the window)Ce modèle est le fondement de la manière dont les bibliothèques implémentent des menus au survol fiables : on conserve un seul écouteur avec propagation à la racine du menu, mais on replie le menu uniquement quand le pointeur quitte toute la sous-arborescence.
Conclusion
Les événements de déplacement de la souris vous permettent de créer des interactions nuancées et réactives autour du pointeur de l'utilisateur. Le point essentiel à retenir est l'association :
- Utilisez
mouseenter/mouseleavepour un état de survol propre, par élément — ils se déclenchent une seule fois et ignorent les éléments enfants. - Utilisez
mouseover/mouseoutquand vous avez besoin de la propagation pour la délégation d'événements, et appuyez-vous surrelatedTargetpour filtrer les transitions vers les descendants.
Pour aller plus loin, consultez les bases des événements souris pour les clics et les boutons, ainsi que l'introduction aux événements du navigateur pour comprendre comment les événements sont connectés en général.