JavaScript: spostamento del mouse — mouseover/out, mouseenter/leave
Scopri le differenze tra mouseover/mouseout e mouseenter/mouseleave in JavaScript: bubbling, relatedTarget, il problema degli elementi figli ed esempi pratici.
Comprendere gli eventi di movimento del mouse in JavaScript: Mouseover, Mouseout, Mouseenter e Mouseleave
Gli eventi di movimento del mouse in JavaScript offrono agli sviluppatori la possibilità di reagire al movimento del cursore sugli elementi di una pagina web. Questi eventi sono essenziali per creare interfacce interattive e reattive che rispondono alle azioni dell'utente. Questa guida esplorerà le differenze tra gli eventi mouseover, mouseout, mouseenter e mouseleave, fornendo esempi pratici per illustrarne l'utilizzo.
Le due coppie a colpo d'occhio
JavaScript mette a disposizione due coppie di eventi per la stessa azione fisica: il puntatore che entra su un elemento e che lo lascia. Sembrano intercambiabili, ma si comportano in modo molto diverso in presenza di elementi figli, e scegliere la coppia sbagliata è uno degli errori UI più comuni.
| Evento | Si attiva quando il puntatore… | Fa bubbling? | Si riattiva sugli elementi figli? |
|---|---|---|---|
mouseover | entra nell'elemento o in qualsiasi discendente | Sì | Sì |
mouseout | lascia l'elemento o qualsiasi discendente | Sì | Sì |
mouseenter | entra nel confine proprio dell'elemento | No | No |
mouseleave | lascia il confine proprio dell'elemento | No | No |
Mouseover e Mouseout
mouseover: Si attiva quando il mouse entra nell'elemento o in uno qualsiasi dei suoi figli. Poiché fa bubbling, è la scelta giusta per la delegazione degli eventi: un unico listener su un contenitore può gestire l'hover su molti elementi figli.mouseout: L'opposto dimouseover— si attiva quando il mouse lascia l'elemento o uno qualsiasi dei suoi figli.
L'insidia è che spostare il puntatore da un genitore a un figlio attiva mouseout sul genitore (il puntatore ha "lasciato" l'area testo del genitore) immediatamente seguito da mouseover (è "entrato" nel figlio, che conta ancora come parte del genitore). Quindi un singolo hover visivo può produrre un flusso caotico di eventi su un genitore con elementi figli.
Mouseenter e Mouseleave
mouseenter: Comemouseover, ma non fa bubbling e non si riattiva quando il puntatore attraversa un elemento figlio. Si attiva esattamente una volta quando il puntatore entra per la prima volta nel confine dell'elemento — perfetto per il comportamento "evidenzia questa card durante l'hover".mouseleave: Si attiva una volta quando il puntatore lascia il confine esterno dell'elemento, ignorando i movimenti tra i discendenti.
Regola pratica: usa
mouseenter/mouseleavequando vuoi una logica pulita del tipo "il puntatore è sopra questo elemento?", emouseover/mouseoutsolo quando hai bisogno del bubbling per la delegazione.
La proprietà relatedTarget
Entrambi gli eventi espongono event.relatedTarget, che indica l'altro elemento coinvolto nella transizione:
- Su
mouseover/mouseenter,relatedTargetè l'elemento da cui proviene il puntatore. - Su
mouseout/mouseleave,relatedTargetè l'elemento verso cui si sta spostando il puntatore.
È così che si riproduce il comportamento di mouseenter usando comunque gli eventi con bubbling mouseover/mouseout: si verifica se relatedTarget è all'interno dell'elemento corrente e si ignora l'evento in tal caso.
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');
});Nota che relatedTarget può essere null — ad esempio quando il puntatore proviene dall'esterno della finestra del browser — quindi verifica questo caso prima di chiamare contains().
Un'insidia: lo "spostamento rapido del mouse"
mouseover/mouseout non garantiscono di attivarsi per ogni elemento su cui passa il puntatore. Se l'utente muove il mouse molto velocemente, gli elementi intermedi possono essere saltati del tutto, e si può ricevere un mouseout per un elemento senza un mouseover corrispondente per quello successivo. Il codice che abbina i due eventi deve tollerare i partner mancanti. mouseenter/mouseleave sono sempre bilanciati per l'elemento a cui sono collegati, il che è un altro motivo per preferirli nel tracciamento degli stati.
Esempi pratici di eventi di movimento del mouse
Questi esempi mostrano come implementare gli eventi di movimento del mouse per migliorare l'esperienza utente tramite elementi interattivi.
Esempio 1: Utilizzo di Mouseover e Mouseout
Questo esempio mostra come cambiare il colore di sfondo di un box quando il cursore del mouse entra ed esce da esso, inclusi i suoi elementi figli.
<!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>Spiegazione:
- L'evento
mouseovercambia il colore di sfondo del box in ciano, anche quando si passa sopra il box interno. - L'evento
mouseoutripristina il colore di sfondo quando il mouse lascia il box, tenendo conto anche del box interno.
Esempio 2: Implementazione di Mouseenter e Mouseleave
Questo esempio migliora l'interazione utente mostrando come usare mouseenter e mouseleave per una reazione più specifica, senza influenzare gli elementi figli.
<!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>Questo esempio dimostra chiaramente come gli eventi mouseenter e mouseleave vengano attivati in modo specifico e non facciano bubbling, consentendo interazioni distinte e isolate con gli elementi annidati.
Esempio 3: Simulare mouseleave con relatedTarget
A volte hai bisogno del bubbling (per poter usare un unico listener delegato) e del comportamento pulito "solo quando il puntatore lascia davvero". Puoi combinarli ascoltando il mouseout con bubbling e ignorando le transizioni verso i discendenti tramite relatedTarget. La logica è la stessa vista sopra, espressa come un piccolo helper riutilizzabile:
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)Questo pattern è la base su cui le librerie implementano menu hover affidabili: si mantiene un unico listener con bubbling sulla radice del menu, ma il menu si chiude solo quando il puntatore lascia l'intero sottoalbero.
Conclusione
Gli eventi di movimento del mouse permettono di costruire interazioni sfumate e reattive attorno al puntatore dell'utente. Il concetto chiave è la distinzione tra le due coppie:
- Usa
mouseenter/mouseleaveper uno stato hover pulito per singolo elemento — si attivano una volta sola e ignorano gli elementi figli. - Usa
mouseover/mouseoutquando hai bisogno del bubbling per la delegazione degli eventi, e affidati arelatedTargetper filtrare le transizioni verso i discendenti.
Per approfondire, consulta le basi degli eventi del mouse per clic e pulsanti, e l'introduzione agli eventi del browser per capire come vengono collegati gli eventi in generale.