W3docs

JavaScript Movendo o mouse: mouseover/out, mouseenter/leave

Aprenda a diferença entre mouseover/mouseout e mouseenter/mouseleave em JavaScript: bubbling, relatedTarget, a armadilha de elementos filhos e exemplos práticos.

Entendendo os Eventos de Movimento do Mouse em JavaScript: Mouseover, Mouseout, Mouseenter e Mouseleave

Os eventos de movimento do mouse em JavaScript fornecem aos desenvolvedores a capacidade de reagir ao movimento do cursor sobre elementos de uma página web. Esses eventos são essenciais para criar interfaces interativas e responsivas que reagem às ações do usuário. Este guia explorará as diferenças entre os eventos mouseover, mouseout, mouseenter e mouseleave, oferecendo exemplos práticos para demonstrar seu uso.

Os dois pares em resumo

O JavaScript oferece dois pares de eventos para a mesma ação física — o ponteiro se movendo sobre um elemento e saindo dele. Eles parecem intercambiáveis, mas se comportam de maneira muito diferente em relação aos elementos filhos, e escolher o par errado é um dos bugs de UI mais comuns.

EventoDispara quando o ponteiro…Faz bubbling?Redispara em elementos filhos?
mouseoverentra no elemento ou em qualquer descendenteSimSim
mouseoutsai do elemento ou de qualquer descendenteSimSim
mouseenterentra no limite do próprio elementoNãoNão
mouseleavesai do limite do próprio elementoNãoNão

Mouseover e Mouseout

  • mouseover: Dispara quando o mouse entra no elemento ou em qualquer um de seus filhos. Como ele faz bubbling, é a escolha certa para delegação de eventos — um único listener em um contêiner pode lidar com o hover sobre muitos itens filhos.
  • mouseout: O inverso de mouseover — dispara quando o mouse sai do elemento ou de qualquer um de seus filhos.

O problema é que mover o ponteiro de um elemento pai para um filho dispara mouseout no pai (o ponteiro "saiu" da área de texto do pai) imediatamente seguido por mouseover (ele "entrou" no filho, que ainda conta como o pai). Assim, um único hover visual pode produzir um fluxo de eventos ruidoso em um pai que possui filhos.

Mouseenter e Mouseleave

  • mouseenter: Semelhante ao mouseover, mas não faz bubbling e não redispara quando o ponteiro cruza para um elemento filho. Ele é acionado exatamente uma vez quando o ponteiro entra pela primeira vez no limite do elemento — perfeito para o comportamento de "destacar este cartão ao passar o mouse".
  • mouseleave: Acionado uma vez quando o ponteiro sai do limite externo do elemento, ignorando o movimento entre descendentes.

Regra geral: use mouseenter/mouseleave quando quiser uma lógica limpa de "o ponteiro está sobre este elemento?", e mouseover/mouseout somente quando precisar de bubbling para delegação.

A propriedade relatedTarget

Ambos os eventos expõem event.relatedTarget, que informa o outro elemento envolvido na transição:

  • Em mouseover/mouseenter, relatedTarget é o elemento de onde o ponteiro veio.
  • Em mouseout/mouseleave, relatedTarget é o elemento para o qual o ponteiro está indo.

É assim que você reproduz o comportamento de mouseenter enquanto ainda usa os eventos com bubbling mouseover/mouseout: verifique se relatedTarget está dentro do elemento atual e ignore o evento caso esteja.

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');
});

Observe que relatedTarget pode ser null — por exemplo, quando o ponteiro vem de fora da janela do navegador — portanto, proteja-se contra isso antes de chamar contains().

Uma armadilha: o "mouse rápido"

mouseover/mouseout não têm garantia de disparar para cada elemento sobre o qual o ponteiro passa. Se o usuário mover o mouse muito rapidamente, elementos intermediários podem ser totalmente ignorados, e você pode receber um mouseout para um elemento sem um mouseover correspondente para o próximo. Código que emparelha os dois eventos deve tolerar parceiros ausentes. mouseenter/mouseleave são sempre balanceados para o elemento ao qual estão vinculados, o que é mais um motivo para preferi-los no rastreamento de estado.

Exemplos Práticos de Eventos de Movimento do Mouse

Estes exemplos demonstram como implementar eventos de movimento do mouse para aprimorar a experiência do usuário por meio de elementos interativos.

Exemplo 1: Usando Mouseover e Mouseout

Este exemplo mostra como alterar a cor de fundo de uma caixa quando o cursor do mouse entra e sai dela, incluindo seus elementos filhos.

<!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>

Explicação:

  • O evento mouseover altera a cor de fundo da caixa para ciano, inclusive ao passar o mouse sobre a caixa interna.
  • O evento mouseout redefine a cor de fundo quando o mouse sai da caixa, considerando também a caixa interna.

Exemplo 2: Implementando Mouseenter e Mouseleave

Este exemplo aprimora a interação do usuário mostrando como usar mouseenter e mouseleave para uma reação mais específica, sem afetar os elementos filhos.

<!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 exemplo demonstra claramente como os eventos mouseenter e mouseleave são acionados especificamente e não fazem bubbling, permitindo interações distintas e isoladas com elementos aninhados.

Exemplo 3: Simulando mouseleave com relatedTarget

Às vezes você precisa de bubbling (para poder usar um único listener delegado) e do comportamento limpo de "somente quando o ponteiro realmente sai". Você pode combiná-los ouvindo o mouseout com bubbling e ignorando transições para descendentes com relatedTarget. A lógica é a mesma vista acima, expressa como um pequeno helper reutilizável:

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 padrão é a base de como as bibliotecas implementam menus de hover confiáveis: manter um único listener com bubbling na raiz do menu, mas recolher o menu somente quando o ponteiro sair de toda a subárvore.

Conclusão

Os eventos de movimento do mouse permitem criar interações sutis e responsivas em torno do ponteiro do usuário. A principal conclusão é o emparelhamento:

  • Use mouseenter/mouseleave para um estado de hover limpo por elemento — eles disparam uma vez e ignoram elementos filhos.
  • Use mouseover/mouseout quando precisar de bubbling para delegação de eventos, e apoie-se em relatedTarget para filtrar transições para descendentes.

Para ir além, revise as noções básicas de eventos de mouse para cliques e botões, e a introdução aos eventos do navegador para entender como os eventos são configurados em geral.

Prática

Prática
Quais são as principais diferenças entre os eventos Mouseover/Mouseout e Mouseenter/Mouseleave em JavaScript?
Quais são as principais diferenças entre os eventos Mouseover/Mouseout e Mouseenter/Mouseleave em JavaScript?
Was this page helpful?