¿Cómo detecto un clic fuera de un elemento?

Tengo algunos menús HTML, que muestro por completo cuando un usuario hace clic en el encabezado de estos menús. Me gustaría ocultar estos elementos cuando el usuario hace clic fuera del área de los menús.

¿Es posible algo como esto con jQuery?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});

preguntado el 30 de septiembre de 08 a las 14:09

Aquí hay una muestra de esta estrategia: jsfiddle.net/tedp/aL7Xe/1 -

Como mencionó Tom, querrás leer css-tricks.com/dangers-stopping-event-propagation antes de utilizar este enfoque. Sin embargo, esa herramienta jsfiddle es bastante buena. -

obtener una referencia al elemento y luego event.target, y finalmente! = o == ambos ejecutan el código en consecuencia .. -

Solución Vanilla JS con event.target y sin event.stopPropagation. -

30 Respuestas

NOTA: Usando stopEventPropagation() es algo que debe evitarse ya que rompe el flujo normal de eventos en el DOM. Ver este artículo para más información. Considere usar este método en su lugar.

Adjunte un evento de clic al cuerpo del documento que cierra la ventana. Adjunte un evento de clic separado al contenedor que detiene la propagación al cuerpo del documento.

$(window).click(function() {
//Hide the menus if visible
});

$('#menucontainer').click(function(event){
    event.stopPropagation();
});

Respondido el 05 de diciembre de 18 a las 10:12

Esto rompe el comportamiento estándar de muchas cosas, incluidos los botones y enlaces, contenidos en #menucontainer. Me sorprende que esta respuesta sea tan popular. - Arte

Esto no interrumpe el comportamiento de nada dentro de #menucontainer, ya que está en la parte inferior de la cadena de propagación para cualquier cosa dentro de él. - Eran Galperin

es muy bonito pero deberías usar $('html').click() no cuerpo. El cuerpo siempre tiene la altura de su contenido. Si no hay mucho contenido o la pantalla es muy alta, solo funciona en la parte que ocupa el cuerpo. - Meo

También me sorprende que esta solución haya obtenido tantos votos. Esto fallará para cualquier elemento externo que tenga stopPropagation jsfiddle.net/Flandre/vaNFw/3 - Andre

Philip Walton explica muy bien por qué esta respuesta no es la mejor solución: css-tricks.com/dangers-stopping-event-propagation - tom

Puedes escuchar un clic evento en document y luego asegúrate #menucontainer no es un antepasado o el destino del elemento en el que se hizo clic mediante el uso .closest().

Si no es así, el elemento en el que se hizo clic está fuera del #menucontainer y puedes esconderlo de forma segura.

$(document).click(function(event) { 
  var $target = $(event.target);
  if(!$target.closest('#menucontainer').length && 
  $('#menucontainer').is(":visible")) {
    $('#menucontainer').hide();
  }        
});

Editar - 2017-06-23

También puede limpiar después del detector de eventos si planea descartar el menú y desea dejar de escuchar eventos. Esta función limpiará solo el oyente recién creado, preservando cualquier otro oyente de clic en document. Con sintaxis ES2015:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    const $target = $(event.target);
    if (!$target.closest(selector).length && $(selector).is(':visible')) {
        $(selector).hide();
        removeClickListener();
    }
  }

  const removeClickListener = () => {
    document.removeEventListener('click', outsideClickListener)
  }

  document.addEventListener('click', outsideClickListener)
}

Editar - 2018-03-11

Para aquellos que no quieren usar jQuery. Aquí está el código anterior en simple vanillaJS (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
        }
    }

    const removeClickListener = () => {
        document.removeEventListener('click', outsideClickListener)
    }

    document.addEventListener('click', outsideClickListener)
}

const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js 

NOTA: Esto se basa en el comentario de Alex para usar !element.contains(event.target) en lugar de la parte jQuery.

Pero element.closest() ahora también está disponible en todos los principales navegadores (la versión de W3C difiere un poco de la de jQuery). Polyfills se puede encontrar aquí: Element.closest ()

Editar - 2020-05-21

En el caso en el que desee que el usuario pueda hacer clic y arrastrar dentro del elemento, luego suelte el mouse fuera del elemento, sin cerrar el elemento:

      ...
      let lastMouseDownX = 0;
      let lastMouseDownY = 0;
      let lastMouseDownWasOutside = false;

      const mouseDownListener = (event: MouseEvent) => {
        lastMouseDownX = event.offsetX
        lastMouseDownY = event.offsetY
        lastMouseDownWasOutside = !$(event.target).closest(element).length
      }
      document.addEventListener('mousedown', mouseDownListener);

Y en outsideClickListener:

const outsideClickListener = event => {
        const deltaX = event.offsetX - lastMouseDownX
        const deltaY = event.offsetY - lastMouseDownY
        const distSq = (deltaX * deltaX) + (deltaY * deltaY)
        const isDrag = distSq > 3
        const isDragException = isDrag && !lastMouseDownWasOutside

        if (!element.contains(event.target) && isVisible(element) && !isDragException) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
          document.removeEventListener('mousedown', mouseDownListener); // Or add this line to removeClickListener()
        }
    }

Respondido 08 Jul 20, 12:07

Probé muchas de las otras respuestas, pero solo esta funcionó. Gracias. El código que terminé usando fue este: $ (document) .click (function (event) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('rápido');}}); - Pistos

De hecho, terminé optando por esta solución porque admite mejor varios menús en la misma página donde hacer clic en un segundo menú mientras el primero está abierto dejará el primero abierto en la solución stopPropagation. - umassthrower

Excelente respuesta. Este es el camino a seguir cuando tiene varios elementos que desea cerrar. - John

Esta debería ser la respuesta aceptada debido a la falla que tiene la otra solución con event.stopPropagation (). - tomate

Sin jQuery - !element.contains(event.target) usar Node.contains () - Alex Ross

¿Cómo detectar un clic fuera de un elemento?

La razón por la que esta pregunta es tan popular y tiene tantas respuestas es que es engañosamente compleja. Después de casi ocho años y decenas de respuestas, estoy realmente sorprendido de ver la poca atención que se le ha dado a la accesibilidad.

Me gustaría ocultar estos elementos cuando el usuario hace clic fuera del área de los menús.

Esta es una causa noble y es la real asunto. El título de la pregunta, que es lo que la mayoría de las respuestas parecen intentar abordar, contiene una desafortunada pista falsa.

Pista: es la palabra "hacer clic"!

En realidad, no desea vincular controladores de clics.

Si está vinculando controladores de clic para cerrar el cuadro de diálogo, ya ha fallado. La razón por la que ha fallado es que no todos activan click eventos. Los usuarios que no usen un mouse podrán escapar de su diálogo (y su menú emergente es posiblemente un tipo de diálogo) presionando lengüeta, y luego no podrán leer el contenido detrás del cuadro de diálogo sin activar posteriormente un click evento.

Así que reformulemos la pregunta.

¿Cómo se cierra un diálogo cuando un usuario termina con él?

Ésta es la meta. Desafortunadamente, ahora necesitamos unir el userisfinishedwiththedialog evento, y ese enlace no es tan sencillo.

Entonces, ¿cómo podemos detectar que un usuario ha terminado de usar un diálogo?

focusout evento

Un buen comienzo es determinar si el foco ha abandonado el cuadro de diálogo.

Sugerencia: tenga cuidado con el blur evento, blur ¡no se propaga si el evento estaba vinculado a la fase de burbujeo!

jQuery's focusout lo hará bien. Si no puede usar jQuery, puede usar blur durante la fase de captura:

element.addEventListener('blur', ..., true);
//                       use capture: ^^^^

Además, para muchos diálogos, deberá permitir que el contenedor se enfoque. Agregar tabindex="-1" para permitir que el diálogo reciba el foco dinámicamente sin interrumpir el flujo de tabulación.

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Si juega con esa demostración durante más de un minuto, debería comenzar a ver problemas rápidamente.

La primera es que no se puede hacer clic en el enlace del cuadro de diálogo. Si intenta hacer clic en él o en la pestaña, se cerrará el cuadro de diálogo antes de que se produzca la interacción. Esto se debe a que enfocar el elemento interno desencadena una focusout evento antes de desencadenar un focusin evento de nuevo.

La solución es poner en cola el cambio de estado en el bucle de eventos. Esto se puede hacer usando setImmediate(...)o setTimeout(..., 0) para navegadores que no son compatibles setImmediate. Una vez en cola, puede ser cancelado por un focusin:

$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>

El segundo problema es que el cuadro de diálogo no se cierra cuando se vuelve a presionar el enlace. Esto se debe a que el cuadro de diálogo pierde el foco, lo que activa el comportamiento de cierre, después de lo cual el clic en el enlace activa el cuadro de diálogo para que se vuelva a abrir.

Al igual que en el problema anterior, es necesario administrar el estado de enfoque. Dado que el cambio de estado ya se ha puesto en cola, es solo una cuestión de manejar eventos de enfoque en los disparadores del diálogo:

Esto debería parecer familiar
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Esc clave

Si pensaba que había terminado con el manejo de los estados de enfoque, hay más que puede hacer para simplificar la experiencia del usuario.

Esta es a menudo una característica "agradable de tener", pero es común que cuando tienes una ventana emergente o modal de cualquier tipo, la Esc La tecla lo cerrará.

keydown: function (e) {
  if (e.which === 27) {
    $(this).removeClass('active');
    e.preventDefault();
  }
}

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('active');
      e.preventDefault();
    }
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Si sabe que tiene elementos enfocables dentro del cuadro de diálogo, no necesitará enfocar el cuadro de diálogo directamente. Si está creando un menú, puede enfocar el primer elemento del menú en su lugar.

click: function (e) {
  $(this.hash)
    .toggleClass('submenu--active')
    .find('a:first')
    .focus();
  e.preventDefault();
}

$('.menu__link').on({
  click: function (e) {
    $(this.hash)
      .toggleClass('submenu--active')
      .find('a:first')
      .focus();
    e.preventDefault();
  },
  focusout: function () {
    $(this.hash).data('submenuTimer', setTimeout(function () {
      $(this.hash).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('submenuTimer'));  
  }
});

$('.submenu').on({
  focusout: function () {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('submenuTimer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('submenu--active');
      e.preventDefault();
    }
  }
});
.menu {
  list-style: none;
  margin: 0;
  padding: 0;
}
.menu:after {
  clear: both;
  content: '';
  display: table;
}
.menu__item {
  float: left;
  position: relative;
}

.menu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}
.menu__link:hover,
.menu__link:focus {
  background-color: black;
  color: lightblue;
}

.submenu {
  border: 1px solid black;
  display: none;
  left: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  top: 100%;
}
.submenu--active {
  display: block;
}

.submenu__item {
  width: 150px;
}

.submenu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}

.submenu__link:hover,
.submenu__link:focus {
  background-color: black;
  color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="menu">
  <li class="menu__item">
    <a class="menu__link" href="#menu-1">Menu 1</a>
    <ul class="submenu" id="menu-1" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
  <li class="menu__item">
    <a  class="menu__link" href="#menu-2">Menu 2</a>
    <ul class="submenu" id="menu-2" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
</ul>
lorem ipsum <a href="http://example.com/">dolor</a> sit amet.


Funciones de WAI-ARIA y otro soporte de accesibilidad

Con suerte, esta respuesta cubre los conceptos básicos de la compatibilidad con el teclado y el mouse accesibles para esta función, pero como ya es bastante considerable, evitaré cualquier discusión sobre Funciones y atributos de WAI-ARIA, Sin embargo, yo altamente Recomiende que los implementadores se refieran a la especificación para obtener detalles sobre qué roles deben usar y cualquier otro atributo apropiado.

Respondido el 20 de junio de 20 a las 12:06

Esta es la respuesta más completa, teniendo en cuenta las explicaciones y la accesibilidad. Creo que esta debería ser la respuesta aceptada, ya que la mayoría de las otras respuestas solo manejan el clic y son solo un fragmento de código eliminado sin ninguna explicación. - Cyrille

Increíble, bien explicado. Acabo de usar este enfoque en un componente React y funcionó perfectamente gracias - David Lavieri

@zzzzBov gracias por la respuesta en profundidad, estoy tratando de lograrlo en Vanilla JS, y estoy un poco perdido con todas las cosas de jquery. ¿Hay algo similar en Vanilla JS? - HendrikEng

@zzzzBov no, no estoy buscando que escribas una versión libre de jQuery, por supuesto, intentaré hacerlo y supongo que lo mejor es hacer una nueva pregunta aquí si realmente me atasco. Muchas gracias de nuevo. - HendrikEng

Si bien este es un gran método para detectar clics en menús desplegables personalizados u otras entradas, nunca debería ser el método preferido para modales o ventanas emergentes por dos razones. El modal se cerrará cuando el usuario cambie a otra pestaña o ventana, o abra un menú contextual, lo cual es realmente molesto. Además, el evento de 'clic' se dispara con el mouse hacia arriba, mientras que el evento de 'focusout' se activa en el instante en que presiona el mouse hacia abajo. Normalmente, un botón solo realiza una acción si presiona el botón del mouse y luego lo suelta. La forma adecuada y accesible de hacer esto para los modales es agregar un botón de cierre con tabulación. - Kevin

Las otras soluciones aquí no funcionaron para mí, así que tuve que usar:

if(!$(event.target).is('#foo'))
{
    // hide menu
}

Respondido 07 Jul 09, 03:07

Publiqué otro ejemplo práctico de cómo usar event.target para evitar activar otro widget de interfaz de usuario de Jquery, haga clic en los controladores html al incrustarlos en su propio cuadro emergente: La mejor forma de conseguir el objetivo original - Joey T

Esto funcionó para mí, excepto que agregué && !$(event.target).parents("#foo").is("#foo") dentro de IF declaración para que los elementos secundarios no cierren el menú al hacer clic. - honyovk

No puedo encontrar algo mejor que: // Estamos fuera de $ (event.target) .parents ('# foo'). Length == 0 - AlexG

La mejora concisa para manejar el anidamiento profundo es usar .is('#foo, #foo *'), pero No recomiendo vincular controladores de clics para resolver este problema. - zzzzBov

!$(event.target).closest("#foo").length sería mejor y obvia la necesidad de la adición de @ honyovk. - tvanc

Tengo una aplicación que funciona de manera similar al ejemplo de Eran, excepto que adjunto el evento de clic al cuerpo cuando abro el menú ... algo así:

$('#menucontainer').click(function(event) {
  $('html').one('click',function() {
    // Hide the menus
  });

  event.stopPropagation();
});

Más información sobre jQuery's one() función

Respondido 26 Feb 16, 17:02

pero luego, si hace clic en el menú en sí, entonces afuera, no funcionará :) - vsync

Ayuda poner event.stopProgagantion () antes de vincular el oyente de clics al cuerpo. - Jasper Kennis

El problema con esto es que "uno" se aplica al método jQuery de agregar eventos a una matriz varias veces. Entonces, si hace clic en el menú para abrirlo más de una vez, el evento se vincula al cuerpo nuevamente e intenta ocultar el menú varias veces. Se debe aplicar una protección contra fallas para solucionar este problema. - marksyzm

Después de encuadernar usando .one - dentro del $('html') manejador - escribe un $('html').off('click'). - Cody

@Cody No creo que eso ayude. La one el manejador llamará automáticamente off (como se muestra en los documentos de jQuery). - Mariano Desanze

Después de investigar, encontré tres soluciones que funcionan (olvidé los enlaces de la página como referencia)

Primera solucion

<script>
    //The good thing about this solution is it doesn't stop event propagation.

    var clickFlag = 0;
    $('body').on('click', function () {
        if(clickFlag == 0) {
            console.log('hide element here');
            /* Hide element here */
        }
        else {
            clickFlag=0;
        }
    });
    $('body').on('click','#testDiv', function (event) {
        clickFlag = 1;
        console.log('showed the element');
        /* Show the element */
    });
</script>

Segunda solución

<script>
    $('body').on('click', function(e) {
        if($(e.target).closest('#testDiv').length == 0) {
           /* Hide dropdown here */
        }
    });
</script>

Tercera solucion

<script>
    var specifiedElement = document.getElementById('testDiv');
    document.addEventListener('click', function(event) {
        var isClickInside = specifiedElement.contains(event.target);
        if (isClickInside) {
          console.log('You clicked inside')
        }
        else {
          console.log('You clicked outside')
        }
    });
</script>

Respondido 30 Oct 16, 19:10

La tercera solución es, con mucho, la forma más elegante de comprobar. Tampoco implica ninguna sobrecarga de jQuery. Muy agradable. Ayudó mucho. Gracias. - dbarth

Estoy tratando de que la tercera solución funcione con varios elementos. document.getElementsByClassName, si alguien tiene una pista, por favor compártala. - lowtechsun

@lowtechsun Tendría que recorrer para verificar cada uno. - Donnie D'Amato

Realmente me gustó la tercera solución, sin embargo, el clic se activa antes de que mi div haya comenzado a mostrar cuál lo oculta nuevamente, ¿alguna idea de por qué? - indiehjaerta

El tercero permite console.log, claro, pero no te permite cerrar realmente configurando display en none, porque hará esto antes de que se muestre el menú. ¿Tienes una solución para esto? - RolandiXor

$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

Funciona para mí muy bien.

respondido 17 nov., 09:09

Este es el que yo usaría. Puede que no sea perfecto, pero como programador aficionado, es lo suficientemente simple como para entenderlo con claridad. - trucha

el desenfoque es un movimiento fuera del #menucontainer la pregunta era sobre un clic - beber

@borrel blur es no un movimiento fuera del contenedor. Desenfocar es lo opuesto al enfoque, estás pensando en mouseout. Esta solución funcionó particularmente bien para mí cuando estaba creando texto "hacer clic para editar" en el que cambiaba de un lado a otro entre texto sin formato y campos de entrada en los clics. - parker.sikand

Tuve que agregar tabindex="-1" de las personas acusadas injustamente llamadas #menuscontainer para que funcione. Parece que si coloca una etiqueta de entrada dentro del contenedor y hace clic en ella, el contenedor se oculta. - Tyrion

el evento mouseleave es más adecuado para menús y contenedores (ref: w3schools.com/jquery/…) - Evgeniya Manolova

Ahora hay un complemento para eso: eventos externos ( blog)

Lo siguiente sucede cuando un haga clic en el exterior handler (WLOG) está vinculado a un elemento:

  • el elemento se agrega a una matriz que contiene todos los elementos con haga clic en el exterior manipuladores
  • un (espacio de nombre) clic el controlador está vinculado al documento (si aún no está allí)
  • en cualquier clic en el documento, el haga clic en el exterior El evento se activa para aquellos elementos en esa matriz que no son iguales o un padre del clic-Eventos objetivo
  • además, el event.target para el haga clic en el exterior el evento se establece en el elemento en el que el usuario hizo clic (por lo que incluso sabe en qué hizo clic el usuario, no solo en el exterior)

Por lo tanto, no se detiene la propagación de eventos y clic Los manipuladores pueden usarse "por encima" del elemento con el manipulador externo.

Respondido 13 Abr '17, 10:04

Buen complemento. El enlace de "eventos externos" está muerto, mientras que el enlace de la publicación de blog está vivo y proporciona un complemento muy útil para eventos de tipo "clickoutside". También tiene licencia del MIT. - TechNyquist

Gran complemento. Funcionó perfectamente. El uso es así: $( '#element' ).on( 'clickoutside', function( e ) { .. } ); - Gavin

¡Esto funcionó para mí perfectamente!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});

respondido 04 mar '14, 18:03

Esta solución funcionó bien para lo que estaba haciendo, donde todavía necesitaba que el evento de clic aumentara, gracias. - Mike

Una solución simple para la situación es:

$(document).mouseup(function (e)
{
    var container = $("YOUR SELECTOR"); // Give you class or ID

    if (!container.is(e.target) &&            // If the target of the click is not the desired div or section
        container.has(e.target).length === 0) // ... nor a descendant-child of the container
    {
        container.hide();
    }
});

El script anterior ocultará el div si fuera del div se activa el evento de clic.

Puede ver el siguiente blog para obtener más información: http://www.codecanal.com/detect-click-outside-div-using-javascript/

Respondido 10 Jul 17, 11:07

No creo que lo que realmente necesita es cerrar el menú cuando el usuario hace clic fuera; lo que necesita es que el menú se cierre cuando el usuario haga clic en cualquier parte de la página. Si hace clic en el menú, o fuera del menú, debería cerrarse, ¿verdad?

No encontrar respuestas satisfactorias arriba me impulsó a escribir esta entrada del blog el otro día. Para los más pedantes, hay una serie de trampas a tener en cuenta:

  1. Si adjunta un controlador de eventos de clic al elemento del cuerpo en el momento del clic, asegúrese de esperar el segundo clic antes de cerrar el menú y desvincular el evento. De lo contrario, el evento de clic que abrió el menú aparecerá en el oyente que tiene que cerrar el menú.
  2. Si usa event.stopPropogation () en un evento de clic, ningún otro elemento de su página puede tener una función de clic en cualquier lugar para cerrar.
  3. Adjuntar un controlador de eventos de clic al elemento del cuerpo de forma indefinida no es una solución eficaz
  4. La comparación del destino del evento y sus padres con el creador del controlador supone que lo que quieres es cerrar el menú cuando haces clic en él, cuando lo que realmente quieres es cerrarlo cuando haces clic en cualquier parte de la página.
  5. Escuchar eventos en el elemento del cuerpo hará que su código sea más frágil. Un estilo tan inocente como este lo rompería: body { margin-left:auto; margin-right: auto; width:960px;}

contestado el 19 de mayo de 11 a las 11:05

"Si hace clic en el menú, o fuera del menú, debería cerrarse, ¿verdad?" no siempre. Cancelar un clic arrastrando un elemento aún activará un clic en el nivel del documento, pero la intención no sería continuar cerrando el menú. También hay muchos otros tipos de cuadros de diálogo que podrían usar el comportamiento de "hacer clic" que permitiría hacer clic internamente. - zzzzBov

Como dijo otro cartel, hay muchas trampas, especialmente si el elemento que está mostrando (en este caso un menú) tiene elementos interactivos. Encontré que el siguiente método es bastante sólido:

$('#menuscontainer').click(function(event) {
    //your code that shows the menus fully

    //now set up an event listener so that clicking anywhere outside will close the menu
    $('html').click(function(event) {
        //check up the tree of the click target to check whether user has clicked outside of menu
        if ($(event.target).parents('#menuscontainer').length==0) {
            // your code to hide menu

            //this event listener has done its job so we can unbind it.
            $(this).unbind(event);
        }

    })
});

Respondido el 22 de diciembre de 11 a las 15:12

Es 2020 y puedes usar event.composedPath()

De: https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath

El método compositePath () de la interfaz Event devuelve la ruta del evento, que es una matriz de los objetos en los que se invocarán los oyentes.

const target = document.querySelector('#myTarget')

document.addEventListener('click', (event) => {
  const withinBoundaries = event.composedPath().includes(target)

  if (withinBoundaries) {
    target.innerText = 'Click happened inside element'
  } else {
    target.innerText = 'Click happened **OUTSIDE** element'
  } 
})
/* just to make it good looking. you don't need this */
#myTarget {
  margin: 50px auto;
  width: 500px;
  height: 500px;
  background: gray;
  border: 10px solid black;
}
<div id="myTarget">
  click me (or not!)
</div>

respondido 03 nov., 20:18

Vaya, nunca supe sobre este método en el objeto de evento, y también en Vanilla JS. ¡Gracias por compartir! - Lislis

si quieres hacerlo en línea, puedes <div style="width:100%;border:1px solid black" onclick="console.log('Outside?',event.composedPath()[0]===this)"><span>Inside</span> Outside</div> - YoniXw

Solution1

En lugar de usar event.stopPropagation () que puede tener algunos efectos secundarios, simplemente defina una variable de bandera simple y agregue una if condición. Probé esto y funcioné correctamente sin ningún efecto secundario de stopPropagation:

var flag = "1";
$('#menucontainer').click(function(event){
    flag = "0"; // flag 0 means click happened in the area where we should not do any action
});

$('html').click(function() {
    if(flag != "0"){
        // Hide the menus if visible
    }
    else {
        flag = "1";
    }
});

Solution2

Con solo un simple if condición:

$(document).on('click', function(event){
    var container = $("#menucontainer");
    if (!container.is(event.target) &&            // If the target of the click isn't the container...
        container.has(event.target).length === 0) // ... nor a descendant of the container
    {
        // Do whatever you want to do when click is outside the element
    }
});

Respondido 30 Oct 16, 19:10

Usé esta solución con una bandera booleana y es buena también con un DOm articulado y también si dentro de #menucontainer hay muchos otros elementos - Migio B

La solución 1 funciona mejor porque maneja los casos en los que el destino del clic se elimina del DOM en el momento en que el evento se propaga al documento. - Alicia

Verifique el destino del evento de clic de la ventana (debe propagarse a la ventana, siempre que no se capture en ningún otro lugar) y asegúrese de que no sea ninguno de los elementos del menú. Si no es así, entonces estás fuera de tu menú.

O verifique la posición del clic y vea si está contenido dentro del área del menú.

Respondido el 30 de Septiembre de 08 a las 17:09

He tenido éxito con algo como esto:

var $menuscontainer = ...;

$('#trigger').click(function() {
  $menuscontainer.show();

  $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
      $menuscontainer.hide();
    }
  });
});

La lógica es: cuando #menuscontainer se muestra, vincule un controlador de clic al cuerpo que oculta #menuscontainer solo si el objetivo (del clic) no es un hijo de él.

Respondido 03 ago 18, 10:08

Me sorprende que nadie lo reconozca focusout evento:

var button = document.getElementById('button');
button.addEventListener('click', function(e){
  e.target.style.backgroundColor = 'green';
});
button.addEventListener('focusout', function(e){
  e.target.style.backgroundColor = '';
});
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <button id="button">Click</button>
</body>
</html>

Respondido el 27 de junio de 20 a las 10:06

Te estás perdiendo mi respuesta, creo. stackoverflow.com/a/47755925/6478359 - Muhammet Can TONBUL

Supongo que nadie sugirió focusout porque la pregunta se trata de hacer clic afuera, no de alejar el mouse. Al igual que con otros, necesito específicamente un clic y pasar el mouse. Pero esto será útil para algunos :) - James

Si el usuario abre el menú y usa la tecla TAB para moverse a los elementos del menú (sin hacer clic en ellos), el uso de su código hace que el menú se cierre solo porque se enfoca en los elementos del menú. Y creo que no es buena idea. - Mohsen Haeri

El evento tiene una propiedad llamada event.path del elemento que es un "lista ordenada estática de todos sus antepasados ​​en orden de árbol". Para verificar si un evento se originó a partir de un elemento DOM específico o uno de sus hijos, simplemente verifique la ruta de ese elemento DOM específico. También se puede utilizar para comprobar varios elementos lógicamente ORVerificar el elemento en el some función.

$("body").click(function() {
  target = document.getElementById("main");
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  })
  if (flag) {
    console.log("Inside")
  } else {
    console.log("Outside")
  }
});
#main {
  display: inline-block;
  background:yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
  <ul>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
  </ul>
</div>
<div id="main2">
  Outside Main
</div>

Entonces, para tu caso, debería ser

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});

Respondido el 27 de diciembre de 19 a las 20:12

event.path no es una cosa. - Andrés

Como variante:

var $menu = $('#menucontainer');
$(document).on('click', function (e) {

    // If element is opened and click target is outside it, hide it
    if ($menu.is(':visible') && !$menu.is(e.target) && !$menu.has(e.target).length) {
        $menu.hide();
    }
});

No tiene problema con detener la propagación de eventos y soporta mejor varios menús en la misma página donde hacer clic en un segundo menú mientras el primero está abierto dejará el primero abierto en la solución stopPropagation.

Respondido 30 Oct 16, 19:10

Encontré este método en algún complemento de calendario de jQuery.

function ClickOutsideCheck(e)
{
  var el = e.target;
  var popup = $('.popup:visible')[0];
  if (popup==undefined)
    return true;

  while (true){
    if (el == popup ) {
      return true;
    } else if (el == document) {
      $(".popup").hide();
      return false;
    } else {
      el = $(el).parent()[0];
    }
  }
};

$(document).bind('mousedown.popup', ClickOutsideCheck);

Respondido 30 Oct 16, 19:10

Aquí está la solución básica de JavaScript para futuros espectadores.

Al hacer clic en cualquier elemento dentro del documento, si la identificación del elemento en el que se hizo clic está activada, o el elemento oculto no está oculto y el elemento oculto no contiene el elemento en el que se hizo clic, cambie el elemento.

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();
<a href="javascript:void(0)" id="toggle">Toggle Hidden Div</a>
<div id="hidden" style="display: none;">This content is normally hidden. click anywhere other than this content to make me disappear</div>

Si va a tener varios conmutadores en la misma página, puede usar algo como esto:

  1. Agrega el nombre de la clase hidden al elemento plegable.
  2. Al hacer clic en el documento, cierre todos los elementos ocultos que no contienen el elemento en el que se hizo clic y no están ocultos
  3. Si el elemento en el que se hizo clic es un alternar, alterne el elemento especificado.

(function () {
    "use strict";
    var hiddenItems = document.getElementsByClassName('hidden'), hidden;
    document.addEventListener('click', function (e) {
        for (var i = 0; hidden = hiddenItems[i]; i++) {
            if (!hidden.contains(e.target) && hidden.style.display != 'none')
                hidden.style.display = 'none';
        }
        if (e.target.getAttribute('data-toggle')) {
            var toggle = document.querySelector(e.target.getAttribute('data-toggle'));
            toggle.style.display = toggle.style.display == 'none' ? 'block' : 'none';
        }
    }, false);
})();
<a href="javascript:void(0)" data-toggle="#hidden1">Toggle Hidden Div</a>
<div class="hidden" id="hidden1" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden2">Toggle Hidden Div</a>
<div class="hidden" id="hidden2" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden3">Toggle Hidden Div</a>
<div class="hidden" id="hidden3" style="display: none;" data-hidden="true">This content is normally hidden</div>

Respondido 30 Oct 16, 19:10

Si está realizando un script para IE y FF 3. * y solo desea saber si el clic se produjo dentro de un área determinada del cuadro, también puede usar algo como:

this.outsideElementClick = function(objEvent, objElement){   
var objCurrentElement = objEvent.target || objEvent.srcElement;
var blnInsideX = false;
var blnInsideY = false;

if (objCurrentElement.getBoundingClientRect().left >= objElement.getBoundingClientRect().left && objCurrentElement.getBoundingClientRect().right <= objElement.getBoundingClientRect().right)
    blnInsideX = true;

if (objCurrentElement.getBoundingClientRect().top >= objElement.getBoundingClientRect().top && objCurrentElement.getBoundingClientRect().bottom <= objElement.getBoundingClientRect().bottom)
    blnInsideY = true;

if (blnInsideX && blnInsideY)
    return false;
else
    return true;}

Respondido 14 ago 09, 18:08

En lugar de utilizar la interrupción del flujo, el evento de desenfoque / enfoque o cualquier otra técnica complicada, simplemente haga coincidir el flujo del evento con el parentesco del elemento:

$(document).on("click.menu-outside", function(event){
    // Test if target and it's parent aren't #menuscontainer
    // That means the click event occur on other branch of document tree
    if(!$(event.target).parents().andSelf().is("#menuscontainer")){
        // Click outisde #menuscontainer
        // Hide the menus (but test if menus aren't already hidden)
    }
});

Para eliminar el detector de eventos de clic fuera, simplemente:

$(document).off("click.menu-outside");

Respondido el 05 de junio de 13 a las 19:06

Para mí esta fue la mejor solución. Lo usé en una devolución de llamada después de la animación, por lo que necesitaba separar el evento en algún lugar. +1 - qwertzman

Aquí hay un cheque redundante. si el elemento es de hecho #menuscontainer todavía estás pasando por sus padres. primero debe verificar eso, y si no es ese elemento, luego suba al árbol DOM. - vsync

Derecha ! Puede cambiar la condición a if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. Es una pequeña optimización, pero ocurre solo unas pocas veces durante la vida de su programa: por cada clic, si el click.menu-outside el evento está registrado. Es más largo (+32 caracteres) y no usa el método de encadenamiento - mems

Uso:

var go = false;
$(document).click(function(){
    if(go){
        $('#divID').hide();
        go = false;
    }
})

$("#divID").mouseover(function(){
    go = false;
});

$("#divID").mouseout(function (){
    go = true;
});

$("btnID").click( function(){
    if($("#divID:visible").length==1)
        $("#divID").hide(); // Toggle
    $("#divID").show();
});

Respondido 22 Feb 17, 16:02

Si alguien tiene curiosidad aquí es la solución javascript (es6):

window.addEventListener('mouseup', e => {
        if (e.target != yourDiv && e.target.parentNode != yourDiv) {
            yourDiv.classList.remove('show-menu');
            //or yourDiv.style.display = 'none';
        }
    })

y es5, por si acaso:

window.addEventListener('mouseup', function (e) {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
    yourDiv.classList.remove('show-menu'); 
    //or yourDiv.style.display = 'none';
}

});

Respondido 18 Jul 17, 03:07

Aquí hay una solución simple por javascript puro. Es actualizado con ES6:

var isMenuClick = false;
var menu = document.getElementById('menuscontainer');
document.addEventListener('click',()=>{
    if(!isMenuClick){
       //Hide the menu here
    }
    //Reset isMenuClick 
    isMenuClick = false;
})
menu.addEventListener('click',()=>{
    isMenuClick = true;
})

respondido 03 nov., 17:10

"Actualizado con ES6" es una afirmación bastante audaz, cuando lo único que está actualizado con ES6 es hacer () => {} en lugar de function() {}. Lo que tienes allí se clasifica como JavaScript simple con un toque de ES6. - MortenMoulder

@MortenMoulder: Sí. Es solo para llamar la atención, aunque en realidad es ES6. Pero mire la solución. Yo creo que es bueno. - Duannx

Es vainilla JS y funciona para el objetivo del evento eliminado de DOM (por ejemplo, cuando se selecciona el valor de la ventana emergente interna, se cierra inmediatamente la ventana emergente). +1 de mi parte! - Alicia

He usado el siguiente script y lo he hecho con jQuery.

jQuery(document).click(function(e) {
    var target = e.target; //target div recorded
    if (!jQuery(target).is('#tobehide') ) {
        jQuery(this).fadeOut(); //if the click element is not the above id will hide
    }
})

A continuación, encuentre el código HTML

<div class="main-container">
<div> Hello I am the title</div>
<div class="tobehide">I will hide when you click outside of me</div>
</div>

Puedes leer el tutorial aquí

Respondido el 14 de enero de 18 a las 11:01

$(document).click(function() {
    $(".overlay-window").hide();
});
$(".overlay-window").click(function() {
    return false;
});

Si hace clic en el documento, oculte un elemento determinado, a menos que haga clic en ese mismo elemento.

Respondido el 20 de Septiembre de 11 a las 22:09

Enganche un detector de eventos de clic en el documento. Dentro del detector de eventos, puede mirar el objeto de evento, en particular, el event.target para ver en qué elemento se hizo clic:

$(document).click(function(e){
    if ($(e.target).closest("#menuscontainer").length == 0) {
        // .closest can help you determine if the element 
        // or one of its ancestors is #menuscontainer
        console.log("hide");
    }
});

contestado el 03 de mayo de 12 a las 13:05

Vota a favor de la respuesta más popular, pero agrega

&& (e.target != $('html').get(0)) // ignore the scrollbar

por lo tanto, un clic en una barra de desplazamiento no [oculta ni lo que sea] su elemento de destino.

Respondido 26 Jul 15, 00:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.