JavaScript Movimiento del ratón: mouseover/out, mouseenter/leave
Aprende la diferencia entre mouseover/mouseout y mouseenter/mouseleave en JavaScript: propagación, relatedTarget, el problema con los elementos hijo y ejemplos ejecutables.
Comprendiendo los eventos de movimiento del ratón en JavaScript: Mouseover, Mouseout, Mouseenter y Mouseleave
Los eventos de movimiento del ratón en JavaScript ofrecen a los desarrolladores la capacidad de reaccionar al movimiento del cursor sobre los elementos de una página web. Estos eventos son esenciales para crear interfaces interactivas y responsivas que respondan a las acciones del usuario. Esta guía explorará las diferencias entre los eventos mouseover, mouseout, mouseenter y mouseleave, con ejemplos prácticos para demostrar su uso.
Los dos pares de un vistazo
JavaScript ofrece dos pares de eventos para la misma acción física — el puntero moviéndose hacia un elemento y alejándose de él. Parecen intercambiables, pero se comportan de manera muy diferente con los elementos hijo, y elegir el par equivocado es uno de los errores de interfaz más comunes.
| Evento | Se dispara cuando el puntero… | ¿Se propaga? | ¿Se vuelve a disparar en elementos hijo? |
|---|---|---|---|
mouseover | entra en el elemento o cualquier descendiente | Sí | Sí |
mouseout | sale del elemento o cualquier descendiente | Sí | Sí |
mouseenter | entra en el límite propio del elemento | No | No |
mouseleave | sale del límite propio del elemento | No | No |
Mouseover y Mouseout
mouseover: Se dispara cuando el ratón entra en el elemento o en cualquiera de sus hijos. Como se propaga, es la opción correcta para la delegación de eventos — un único listener en un contenedor puede gestionar el hovering sobre muchos elementos hijo.mouseout: El espejo demouseover— se dispara cuando el ratón sale del elemento o de cualquiera de sus hijos.
El problema es que al mover el puntero desde un elemento padre hacia un hijo se dispara mouseout en el padre (el puntero "salió" del área de texto del padre) inmediatamente seguido de mouseover (el puntero "entró" en el hijo, que aún cuenta como parte del padre). Así, un único hover visual puede generar un flujo ruidoso de eventos en un padre que tiene hijos.
Mouseenter y Mouseleave
mouseenter: Similar amouseover, pero no se propaga y no se vuelve a disparar cuando el puntero cruza hacia un elemento hijo. Se activa exactamente una vez cuando el puntero entra por primera vez en el límite del elemento — perfecto para el comportamiento de "resaltar esta tarjeta mientras esté sobre ella".mouseleave: Se activa una vez cuando el puntero sale del límite externo del elemento, ignorando el movimiento entre descendientes.
Regla general: usa
mouseenter/mouseleavecuando quieras una lógica limpia de "¿está el puntero sobre este elemento?", ymouseover/mouseoutsolo cuando necesites propagación para delegación.
La propiedad relatedTarget
Ambos eventos exponen event.relatedTarget, que indica el otro elemento involucrado en la transición:
- En
mouseover/mouseenter,relatedTargetes el elemento del que proviene el puntero. - En
mouseout/mouseleave,relatedTargetes el elemento al que se dirige el puntero.
Así es como se puede reproducir el comportamiento de mouseenter usando aún los eventos con propagación mouseover/mouseout: comprobar si relatedTarget está dentro del elemento actual e ignorar el evento si es así.
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');
});Ten en cuenta que relatedTarget puede ser null — por ejemplo, cuando el puntero viene desde fuera de la ventana del navegador — así que compruébalo antes de llamar a contains().
Un problema: el "movimiento rápido del ratón"
mouseover/mouseout no garantizan que se disparen para cada elemento sobre el que pasa el puntero. Si el usuario mueve el ratón muy rápidamente, es posible que se omitan elementos intermedios por completo, y puedes recibir un mouseout para un elemento sin el mouseover correspondiente para el siguiente. El código que empareja ambos eventos debe tolerar compañeros faltantes. mouseenter/mouseleave siempre están equilibrados para el elemento al que están asociados, lo que es otra razón para preferirlos en el seguimiento de estado.
Ejemplos prácticos de eventos de movimiento del ratón
Estos ejemplos demuestran cómo implementar eventos de movimiento del ratón para mejorar la experiencia del usuario mediante elementos interactivos.
Ejemplo 1: Usando Mouseover y Mouseout
Este ejemplo muestra cómo cambiar el color de fondo de una caja cuando el cursor del ratón entra y sale de ella, incluidos sus elementos hijo.
<!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>Explicación:
- El evento
mouseovercambia el color de fondo de la caja a cian, incluso al pasar el cursor sobre la caja interior. - El evento
mouseoutrestablece el color de fondo cuando el ratón sale de la caja, teniendo en cuenta también la caja interior.
Ejemplo 2: Implementando Mouseenter y Mouseleave
Este ejemplo mejora la interacción del usuario mostrando cómo usar mouseenter y mouseleave para una reacción más específica, sin afectar a los elementos hijo.
<!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>Este ejemplo demuestra claramente cómo los eventos mouseenter y mouseleave se activan de forma específica y no se propagan, lo que permite interacciones distintas y aisladas con elementos anidados.
Ejemplo 3: Simulando mouseleave con relatedTarget
A veces se necesita propagación (para poder usar un único listener delegado) y el comportamiento limpio de "solo cuando el puntero realmente sale". Puedes combinarlos escuchando el evento mouseout con propagación e ignorando las transiciones hacia descendientes con relatedTarget. La lógica es la misma que viste antes, expresada como un pequeño ayudante reutilizable:
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)Este patrón es la base de cómo las bibliotecas implementan menús hover fiables: mantén un único listener con propagación en la raíz del menú, pero colapsa el menú solo cuando el puntero sale de todo el subárbol.
Conclusión
Los eventos de movimiento del ratón te permiten crear interacciones matizadas y responsivas en torno al puntero del usuario. La conclusión clave es el emparejamiento:
- Usa
mouseenter/mouseleavepara un estado de hover limpio por elemento — se disparan una vez e ignoran los elementos hijo. - Usa
mouseover/mouseoutcuando necesites propagación para delegación de eventos, y apóyate enrelatedTargetpara filtrar las transiciones hacia descendientes.
Para profundizar más, revisa los fundamentos de eventos del ratón para clics y botones, y la introducción a los eventos del navegador para entender cómo se conectan los eventos en general.