He buscado una buena solución en todas partes, pero no puedo encontrar una que no use jQuery .
¿Existe una forma normal entre navegadores (sin hacks extraños o código fácil de descifrar) para detectar un clic fuera de un elemento (que puede tener o no hijos)?
javascript
Tiberiu-Ionuț Stan
fuente
fuente
Respuestas:
Agregue un detector de eventos
document
y utilíceloNode.contains()
para encontrar si el objetivo del evento (que es el elemento más interno en el que se hace clic) está dentro del elemento especificado. Funciona incluso en IE5var specifiedElement = document.getElementById('a'); //I'm using "click" but it works with any event document.addEventListener('click', function(event) { var isClickInside = specifiedElement.contains(event.target); if (!isClickInside) { //the click was outside the specifiedElement, do something } });
Mostrar fragmento de código
var specifiedElement = document.getElementById('a'); //I'm using "click" but it works with any event document.addEventListener('click', function(event) { var isClickInside = specifiedElement.contains(event.target); if (isClickInside) { alert('You clicked inside A') } else { alert('You clicked outside A') } });
div { margin: auto; padding: 1em; max-width: 6em; background: rgba(0, 0, 0, .2); text-align: center; }
Is the click inside A or outside? <div id="a">A <div id="b">B <div id="c">C</div> </div> </div>
fuente
Debe manejar el evento de clic en el nivel del documento. En el objeto de evento, tiene una
target
propiedad, el elemento DOM más interno en el que se hizo clic. Con esto se revisa y recorre sus padres hasta el elemento del documento, si uno de ellos es su elemento observado.Vea el ejemplo en jsFiddle
document.addEventListener("click", function (e) { var level = 0; for (var element = e.target; element; element = element.parentNode) { if (element.id === 'x') { document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked"; return; } level++; } document.getElementById("out").innerHTML = "not x clicked"; });
Como siempre, esto no es compatible con navegadores defectuosos debido a addEventListener / attachEvent, pero funciona así.
Se hace clic en un niño, cuando no es event.target, pero uno de sus padres es el elemento observado (simplemente estoy contando el nivel para esto). También puede tener una var booleana, si el elemento se encuentra o no, para no devolver el controlador desde dentro de la cláusula for. Mi ejemplo se limita a que el controlador solo termina, cuando nada coincide.
Agregando compatibilidad entre navegadores, generalmente lo hago así:
var addEvent = function (element, eventName, fn, useCapture) { if (element.addEventListener) { element.addEventListener(eventName, fn, useCapture); } else if (element.attachEvent) { element.attachEvent(eventName, function (e) { fn.apply(element, arguments); }, useCapture); } };
Este es un código compatible con varios navegadores para adjuntar un detector / manejador de eventos, incluida la reescritura
this
en IE, para que sea el elemento, como lo hace jQuery para sus manejadores de eventos. Hay muchos argumentos para tener en cuenta algunos bits de jQuery;)fuente
target
propiedad (que, como dice metadings, será el elemento DOM más interno) y todos sus elementos principales. O llega al elemento observado, lo que significa que el usuario hizo clic dentro de él, o llega al elemento del documento, en cuyo caso hizo clic fuera. Alternativamente, puede agregar dos detectores de clics, uno en el elemento del documento y otro en el elemento observado, que usaevent.stopPropagation()
/cancelBubble
para que el detector del elemento del documento solo se active con un clic fuera del elemento observado.for
. Por curiosidad, ¿habría alguna razón para no comparar un elemento de una variable con el objetivo en el evento (usando una comparación estricta)?Qué tal esto:
jsBin demo
document.onclick = function(event){ var hasParent = false; for(var node = event.target; node != document.body; node = node.parentNode) { if(node.id == 'div1'){ hasParent = true; break; } } if(hasParent) alert('inside'); else alert('outside'); }
fuente
Para ocultar el elemento haciendo clic fuera de él, generalmente aplico un código tan simple:
var bodyTag = document.getElementsByTagName('body'); var element = document.getElementById('element'); function clickedOrNot(e) { if (e.target !== element) { // action in the case of click outside bodyTag[0].removeEventListener('click', clickedOrNot, true); } } bodyTag[0].addEventListener('click', clickedOrNot, true);
fuente
Otro enfoque muy simple y rápido para este problema es mapear la matriz de
path
en el objeto de evento devuelto por el oyente. Si el nombreid
oclass
de su elemento coincide con uno de los de la matriz, el clic está dentro de su elemento.(Esta solución puede ser útil si no desea obtener el elemento directamente (por ejemplo:,
document.getElementById('...')
por ejemplo, en una aplicación reactjs / nextjs, en ssr ..).Aquí hay un ejemplo:
document.addEventListener('click', e => { let clickedOutside = true; e.path.forEach(item => { if (!clickedOutside) return; if (item.className === 'your-element-class') clickedOutside = false; }); if (clickedOutside) // Make an action if it's clicked outside.. });
¡Espero que esta respuesta te ayude! (Avíseme si mi solución no es una buena solución o si ve algo para mejorar).
fuente
Investigué mucho para encontrar un método mejor. El método JavaScript .contains va de forma recursiva en DOM para comprobar si contiene el objetivo o no. Lo usé en uno de los proyectos de reacción, pero cuando el DOM de reacción cambia en el estado establecido, el método .contains no funciona. Entonces se me ocurrió esta solución
//Basic Html snippet <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="mydiv"> <h2> click outside this div to test </h2> Check click outside </div> </body> </html> //Implementation in Vanilla javaScript const node = document.getElementById('mydiv') //minor css to make div more obvious node.style.width = '300px' node.style.height = '100px' node.style.background = 'red' let isCursorInside = false //Attach mouseover event listener and update in variable node.addEventListener('mouseover', function() { isCursorInside = true console.log('cursor inside') }) /Attach mouseout event listener and update in variable node.addEventListener('mouseout', function() { isCursorInside = false console.log('cursor outside') }) document.addEventListener('click', function() { //And if isCursorInside = false it means cursor is outside if(!isCursorInside) { alert('Outside div click detected') } })
TRABAJANDO DEMO jsfiddle
fuente