Evite onmouseout al pasar el elemento secundario del div absoluto principal SIN jQuery

169

Estoy teniendo problemas con la onmouseoutfunción en un div positonado absoluto. Cuando el mouse golpea un elemento hijo en el div, el evento mouseout se dispara, pero no quiero que se active hasta que el mouse esté fuera del padre, div absoluto.

¿Cómo puedo evitar que el mouseoutevento se active cuando golpea un elemento hijo SIN jquery?

Sé que esto tiene algo que ver con el burbujeo de eventos, pero no tengo suerte en descubrir cómo resolver esto.

Encontré una publicación similar aquí: ¿Cómo deshabilitar los eventos de mouseout activados por elementos secundarios?

Sin embargo, esa solución usa jQuery.

Juan
fuente
Terminé resolviendo el problema usando un tiempo de espera y despejándolo al pasar el cursor sobre los elementos secundarios
John
Hola, he visto tu demo, pero cuando reviso los elementos en el panel inferior derecho, ¿no pasa nada?
John
1
Encontró esta respuesta si está buscando evitar un mouseout en el evento principal cuando pasa el mouse sobre un elemento secundario: javascript mouseover / mouseout .
user823527
1
Mira la respuesta de
craigmichaelmartin

Respuestas:

94
function onMouseOut(event) {
        //this is the original element the event handler was assigned to
        var e = event.toElement || event.relatedTarget;
        if (e.parentNode == this || e == this) {
           return;
        }
    alert('MouseOut');
    // handle mouse event here!
}



document.getElementById('parent').addEventListener('mouseout',onMouseOut,true);

Hice una demostración rápida de JsFiddle, con todo el CSS y HTML necesarios, échale un vistazo ...

EDITAR enlace FIJO para compatibilidad entre navegadores http://jsfiddle.net/RH3tA/9/

TENGA EN CUENTA que esto solo verifica el elemento primario inmediato, si el elemento primario div había anidado elementos secundarios, entonces tiene que atravesar de alguna manera los elementos primarios que buscan el "elemento original"

EDITAR ejemplo para niños anidados

EDITAR Corregido para con suerte navegador cruzado

function makeMouseOutFn(elem){
    var list = traverseChildren(elem);
    return function onMouseOut(event) {
        var e = event.toElement || event.relatedTarget;
        if (!!~list.indexOf(e)) {
            return;
        }
        alert('MouseOut');
        // handle mouse event here!
    };
}

//using closure to cache all child elements
var parent = document.getElementById("parent");
parent.addEventListener('mouseout',makeMouseOutFn(parent),true);

//quick and dirty DFS children traversal, 
function traverseChildren(elem){
    var children = [];
    var q = [];
    q.push(elem);
    while (q.length > 0) {
      var elem = q.pop();
      children.push(elem);
      pushAll(elem.children);
    }
    function pushAll(elemArray){
      for(var i=0; i < elemArray.length; i++) {
        q.push(elemArray[i]);
      }
    }
    return children;
}

Y un nuevo enlace actualizado JSFiddle , EDIT

Amjad Masad
fuente
1
Debe agregar un evento de mouseout al violín para que pueda probarse. Una alerta o algo debería hacer.
John
Lo siento, parece que tienes alerta ('MouseOut'), ¿pero no me funciona? He golpeado ejecutar sin opción de biblioteca JS
John
Probado en safari y cromo, ¡pero debería funcionar en todo! cual es tu navegador
Amjad Masad
2
¿Qué pasa si tienes nietos? o bisnietos? es decir, ¿puede tener una solución recursiva en lugar de simplemente imitar a los niños?
Roozbeh15
20
Esto no funciona mouseleavees la respuesta correcta
Code Whisperer
126

Uso onmouseleave.

O, en jQuery, use mouseleave()

Es exactamente lo que estás buscando. Ejemplo:

<div class="outer" onmouseleave="yourFunction()">
    <div class="inner">
    </div>
</div>

o, en jQuery:

$(".outer").mouseleave(function(){
    //your code here
});

Un ejemplo está aquí .

PHPWannaBePro
fuente
3
onMouseLeavees lo que terminé usando también, ya que no burbujea.
Alecz
Me ahorró mucho tiempo. thnx
npo
mouseleaveNo puede ser cancelable.
grecdev
@grecdev, ¡di más!
Ben Wheeler
97

Para una solución CSS pura más simple que funcione en algunos casos ( IE11 o más reciente ), uno podría eliminar los niños pointer-eventsconfigurándolos ennone

.parent * {
     pointer-events: none;
}
Zach Saucier
fuente
44
¡Brillante! Totalmente solo solucioné mi problema!
Anna_MediaGirl
44
Esta debería ser la primera o al menos la segunda respuesta, estaba listo para seguir con la molestia de los oyentes de eventos y esto también solucionó totalmente mi problema. Tan sencillo !
Mickael V.
9
Esto evita que se dispare el mouseout, pero para mí también evita que se haga clic en el niño real como una selección en el menú, que es el punto del menú.
johnbakers
@hellofunk Es por eso que aplicaría el evento click al padre. El código exacto necesario varía según la implementación, esta respuesta solo sugiere usar la pointer-eventspropiedad
Zach Saucier
1
funciona solo si no tiene otros elementos de controlador (editar, eliminar) dentro del área, porque esta solución también los bloquea ..
Zoltán Süle
28

Aquí hay una solución más elegante basada en lo que vino a continuación. explica el evento que brota de más de un nivel de niños. También tiene en cuenta los problemas entre navegadores.

function onMouseOut(this, event) {
//this is the original element the event handler was assigned to
   var e = event.toElement || event.relatedTarget;

//check for all children levels (checking from bottom up)
while(e && e.parentNode && e.parentNode != window) {
    if (e.parentNode == this||  e == this) {
        if(e.preventDefault) e.preventDefault();
        return false;
    }
    e = e.parentNode;
}

//Do something u need here
}

document.getElementById('parent').addEventListener('mouseout',onMouseOut,true);
Sam-Elie
fuente
Sam Elie, parece que esto no funciona en IE, Safari u Opera. ¿Tiene una versión de este script para varios navegadores? Funciona de maravilla en Firefox y Chrome. Gracias.
1
Cuando pego la función anterior en mi script, Dreamweaver y FF arrojan un error en la primera línea, "function onMouseOut (this, event) {". Parece que no puedes poner "esto" como parámetro. Supongo que funciona cuando dejo de lado "esto" en los parámetros de entrada, pero no estoy seguro, porque "No sé lo que no sé".
TARKUS
1
También tuve el mismo problema emergente en Chrome en la misma línea, excluyendo thisde la línea de definición de función que @GregoryLewis mencionó solucionó el problema. También para obtener más compatibilidad entre navegadores, intente esto: if (window.addEventListener) {document.getElementById ('parent'). AddEventListener ('mouseout', onMouseOut, true); } else if (window.attachEvent) {document.getElementById ('parent'). attachEvent ("onmouseout", onMouseOut); }
DWils
¡Muy guapo! La mejor respuesta si se requiere soporte para navegadores antiguos.
BurninLeo
13

Gracias a Amjad Masad que me inspiró.

Tengo la siguiente solución que parece funcionar en IE9, FF y Chrome y el código es bastante corto (sin el cierre complejo y los elementos secundarios transversales):

    DIV.onmouseout=function(e){
        // check and loop relatedTarget.parentNode
        // ignore event triggered mouse overing any child element or leaving itself
        var obj=e.relatedTarget;
        while(obj!=null){
            if(obj==this){
                return;
            }
            obj=obj.parentNode;
        }
        // now perform the actual action you want to do only when mouse is leaving the DIV
    }
Lawrence Mok
fuente
13

en lugar de onmouseout, use onmouseleave.

No nos ha mostrado su código específico, por lo que no puedo mostrarle en su ejemplo específico cómo hacerlo.

Pero es muy simple: simplemente reemplace onmouseout con onmouseleave.

Eso es todo :) Entonces, simple :)

Si no está seguro de cómo hacerlo, vea la explicación en:

https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_onmousemove_leave_out

Paz de pastel :) Disfrútalo :)

kavi
fuente
Este es un gran grito, vale la pena echarle un vistazo si tiene una situación similar
Chris
12

Si está utilizando jQuery, también puede utilizar la función "mouseleave", que se ocupa de todo esto por usted.

$('#thetargetdiv').mouseenter(do_something);
$('#thetargetdiv').mouseleave(do_something_else);

do_something se disparará cuando el mouse entre en thetargetdiv o cualquiera de sus hijos, do_something_else solo se disparará cuando el mouse abandone thetargetdiv y cualquiera de sus hijos.

Jamie Brown
fuente
8

Creo que Quirksmode tiene todas las respuestas que necesita (comportamiento de burbujeo de diferentes navegadores y eventos de mouseenter / mouseleave ), pero creo que la conclusión más común de ese desastre de burbujeo de eventos es el uso de un marco como JQuery o Mootools (que tiene el mouseenter y mouseleave eventos, que son exactamente lo que intuyó que sucedería).

Eche un vistazo a cómo lo hacen, si lo desea, hágalo usted mismo
o puede crear su versión personalizada de "Lean mean" de Mootools con solo la parte del evento (y sus dependencias).

Rubén
fuente
7

Tratar mouseleave()

Ejemplo:

<div id="parent" mouseleave="function">
   <div id="child">

   </div>
</div>

;)

Jean-Philippe
fuente
5

He encontrado una solución muy simple,

simplemente use el evento onmouseleave = "myfunc ()" que el evento onmousout = "myfunc ()"

En mi código funcionó !!

Ejemplo:

<html>
<head>
<script type="text/javascript">
   function myFunc(){
      document.getElementById('hide_div').style.display = 'none';
   }
   function ShowFunc(){
      document.getElementById('hide_div').style.display = 'block';
   }
</script>
</head>
<body>
<div onmouseleave="myFunc()" style='border:double;width:50%;height:50%;position:absolute;top:25%;left:25%;'>
   Hover mouse here
   <div id='child_div' style='border:solid;width:25%;height:25%;position:absolute;top:10%;left:10%;'>
      CHILD <br/> It doesn't fires if you hover mouse over this child_div
   </div>
</div>
<div id="hide_div" >TEXT</div>
<a href='#' onclick="ShowFunc()">Show "TEXT"</a>
</body>
</html>

Mismo ejemplo con la función mouseout:

<html>
<head>
<script type="text/javascript">
   function myFunc(){
      document.getElementById('hide_div').style.display = 'none';
   }
   function ShowFunc(){
      document.getElementById('hide_div').style.display = 'block';
   }
</script>
</head>
<body>
<div onmouseout="myFunc()" style='border:double;width:50%;height:50%;position:absolute;top:25%;left:25%;'>
   Hover mouse here
   <div id='child_div' style='border:solid;width:25%;height:25%;position:absolute;top:10%;left:10%;'>
      CHILD <br/> It fires if you hover mouse over this child_div
   </div>
</div>
<div id="hide_div">TEXT</div>
<a href='#' onclick="ShowFunc()">Show "TEXT"</a>
</body>
</html>

Espero eso ayude :)

Arminio
fuente
2

Hay dos formas de manejar esto.

1) Verifique el resultado event.target en su devolución de llamada para ver si coincide con su div padre

var g_ParentDiv;

function OnMouseOut(event) {
    if (event.target != g_ParentDiv) {
        return;
    }
    // handle mouse event here!
};


window.onload = function() {
    g_ParentDiv = document.getElementById("parentdiv");
    g_ParentDiv.onmouseout = OnMouseOut;
};

<div id="parentdiv">
    <img src="childimage.jpg" id="childimg" />
</div>

2) O utilice la captura de eventos y llame a event.stopPropagation en la función de devolución de llamada

var g_ParentDiv;

function OnMouseOut(event) {

    event.stopPropagation(); // don't let the event recurse into children

    // handle mouse event here!
};


window.onload = function() {
    g_ParentDiv = document.getElementById("parentdiv");
    g_ParentDiv.addEventListener("mouseout", OnMouseOut, true); // pass true to enable event capturing so parent gets event callback before children
};

<div id="parentdiv">
    <img src="childimage.jpg" id="childimg" />
</div>
selbie
fuente
¿Cómo puedo hacer que esto funcione para onmouseout en lugar de onmouseover?
John
Ups Culpa mía. Acabo de cambiar todas las referencias de "mouseover" a "mouseout" en el código anterior. Eso debería hacerlo por ti.
selbie
Para el primer método, event.target siempre coincide con el elemento primario cuando el mouse pasa sobre el elemento secundario, por lo que no funciona
John
Lo mismo ocurre para el segundo método. Cuando el mouse ingresa a un elemento hijo, aún activa el evento
John
1

Hago que funcione como un encanto con esto:

function HideLayer(theEvent){
 var MyDiv=document.getElementById('MyDiv');
 if(MyDiv==(!theEvent?window.event:theEvent.target)){
  MyDiv.style.display='none';
 }
}

Ah, y la MyDivetiqueta es así:

<div id="MyDiv" onmouseout="JavaScript: HideLayer(event);">
 <!-- Here whatever divs, inputs, links, images, anything you want... -->
<div>

De esta manera, cuando onmouseout va a un hijo, nieto, etc., style.display='none'no se ejecuta; pero cuando onmouseout sale de MyDiv se ejecuta.

Por lo tanto, no es necesario detener la propagación, usar temporizadores, etc.

Gracias por los ejemplos, podría hacer este código a partir de ellos.

Espero que esto ayude a alguien.

También se puede mejorar así:

function HideLayer(theLayer,theEvent){
 if(theLayer==(!theEvent?window.event:theEvent.target)){
  theLayer.style.display='none';
 }
}

Y luego las etiquetas DIV como esta:

<div onmouseout="JavaScript: HideLayer(this,event);">
 <!-- Here whatever divs, inputs, links, images, anything you want... -->
<div>

De manera más general, no solo para un div y no es necesario agregar id="..."en cada capa.

z666zz666z
fuente
1

Si tiene acceso al elemento al que se adjunta el evento dentro del mouseoutmétodo, puede usar contains()para ver sievent.relatedTarget es un elemento hijo o no.

Como event.relatedTargetes el elemento al que ha pasado el mouse, si no es un elemento secundario, se ha sacado el elemento del mouse.

div.onmouseout = function (event) {
    if (!div.contains(event.relatedTarget)) {
        // moused out of div
    }
}
astronauta
fuente
1

En angular 5, 6 y 7

<div (mouseout)="onMouseOut($event)"
     (mouseenter)="onMouseEnter($event)"></div>

Luego en

import {Component,Renderer2} from '@angular/core';
...
@Component({
 selector: 'app-test',
 templateUrl: './test.component.html',
 styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit, OnDestroy {
...
 public targetElement: HTMLElement;

 constructor(private _renderer: Renderer2) {
 }

 ngOnInit(): void {
 }

 ngOnDestroy(): void {
  //Maybe reset the targetElement
 }

 public onMouseEnter(event): void {
  this.targetElement = event.target || event.srcElement;
  console.log('Mouse Enter', this.targetElement);
 }

 public onMouseOut(event): void {
  const elementRelated = event.toElement || event.relatedTarget;
  if (this.targetElement.contains(elementRelated)) {
    return;
  }
  console.log('Mouse Out');
 }
}
Alan Torrico
fuente
0

Verifico el desplazamiento del elemento original para obtener las coordenadas de página de los límites del elemento, luego me aseguro de que la acción del mouseout solo se active cuando el mouseout está fuera de esos límites. Sucio pero funciona.

$(el).live('mouseout', function(event){
    while(checkPosition(this, event)){
        console.log("mouseovering including children")
    }
    console.log("moused out of the whole")
})

var checkPosition = function(el, event){
    var position = $(el).offset()
    var height = $(el).height()
    var width = $(el).width()
    if (event.pageY > position.top 
|| event.pageY < (position.top + height) 
|| event.pageX > position.left 
|| event.pageX < (position.left + width)){
    return true
}
}
saranicole
fuente
0
var elem = $('#some-id');
elem.mouseover(function () {
   // Some code here
}).mouseout(function (event) {
   var e = event.toElement || event.relatedTarget;
   if (elem.has(e).length > 0) return;

   // Some code here
});
Michael Vaganov
fuente
Por favor, dé alguna explicación con su respuesta.
Sebass van Boxel
0

Si agregó (o tiene) una clase o id de CSS al elemento padre, puede hacer algo como esto:

<div id="parent">
  <div>
  </div>
</div>

JavaScript:
document.getElementById("parent").onmouseout = function(e) {
  e = e ? e : window.event //For IE
  if(e.target.id == "parent") {
    //Do your stuff
  }
}

Entonces, las cosas solo se ejecutan cuando el evento está en el div padre.

Karthik Palaniappan
fuente
0

Solo quería compartir algo contigo.
Me costó un poco con ng-mouseentery ng-mouseleaveeventos.

El caso de estudio:

Creé un menú de navegación flotante que se alterna cuando el cursor está sobre un icono.
Este menú estaba en la parte superior de cada página.

  • Para manejar mostrar / ocultar en el menú, alterno una clase.
    ng-class="{down: vm.isHover}"
  • Para alternar vm.isHover , uso los eventos ng mouse.
    ng-mouseenter="vm.isHover = true"
    ng-mouseleave="vm.isHover = false"

Por ahora, todo estaba bien y funcionó como se esperaba.
La solución es limpia y simple.

El problema entrante:

En una vista específica, tengo una lista de elementos.
Agregué un panel de acción cuando el cursor está sobre un elemento de la lista.
Usé el mismo código que el anterior para manejar el comportamiento.

El problema:

Descubrí que cuando mi cursor está en el menú de navegación flotante y también en la parte superior de un elemento, hay un conflicto entre ellos.
El panel de acción apareció y la navegación flotante fue ocultar.

La cuestión es que incluso si el cursor está sobre el menú de navegación flotante, se activa el elemento de lista ng-mouseenter.
No tiene sentido para mí, porque esperaría una interrupción automática de los eventos de propagación del mouse.
Debo decir que me sentí decepcionado y dedico algo de tiempo a descubrir ese problema.

Primeros pensamientos:

Traté de usar estos:

  • $event.stopPropagation()
  • $event.stopImmediatePropagation()

Combiné muchos eventos de puntero ng (mousemove, mouveover, ...) pero ninguno me ayudó.

Solución CSS:

Encontré la solución con una propiedad css simple que uso cada vez más:

pointer-events: none;

Básicamente, lo uso así (en los elementos de mi lista):

ng-style="{'pointer-events': vm.isHover ? 'none' : ''}"

Con este complicado, los eventos ng-mouse ya no se activarán y mi menú de navegación flotante ya no se cerrará cuando el cursor esté sobre él y sobre un elemento de la lista.

Para llegar más lejos:

Como es de esperar, esta solución funciona pero no me gusta.
No controlamos nuestros eventos y es malo.
Además, debe tener acceso al vm.isHoveralcance para lograrlo y puede que no sea posible o posible, pero sucio de alguna manera u otra.
Podría hacer un violín si alguien quiere mirar.

Sin embargo, no tengo otra solución ...
Es una larga historia y no puedo darte una papa, así que perdóname amigo.
De todos modos, pointer-events: nonees la vida, así que recuérdalo.

C0ZEN
fuente