W3docs

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.

EventoSe dispara cuando el puntero…¿Se propaga?¿Se vuelve a disparar en elementos hijo?
mouseoverentra en el elemento o cualquier descendiente
mouseoutsale del elemento o cualquier descendiente
mouseenterentra en el límite propio del elementoNoNo
mouseleavesale del límite propio del elementoNoNo

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 de mouseover — 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 a mouseover, 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/mouseleave cuando quieras una lógica limpia de "¿está el puntero sobre este elemento?", y mouseover/mouseout solo 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, relatedTarget es el elemento del que proviene el puntero.
  • En mouseout/mouseleave, relatedTarget es 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 mouseover cambia el color de fondo de la caja a cian, incluso al pasar el cursor sobre la caja interior.
  • El evento mouseout restablece 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/mouseleave para un estado de hover limpio por elemento — se disparan una vez e ignoran los elementos hijo.
  • Usa mouseover/mouseout cuando necesites propagación para delegación de eventos, y apóyate en relatedTarget para 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.

Práctica

Práctica
¿Cuáles son las diferencias clave entre los eventos Mouseover/Mouseout y Mouseenter/Mouseleave en JavaScript?
¿Cuáles son las diferencias clave entre los eventos Mouseover/Mouseout y Mouseenter/Mouseleave en JavaScript?
Was this page helpful?