Question Comment puis-je détecter un clic en dehors d'un élément?


J'ai quelques menus HTML, que je montre complètement quand un utilisateur clique sur la tête de ces menus. Je voudrais cacher ces éléments lorsque l'utilisateur clique en dehors de la zone des menus.

Est-ce que quelque chose comme ça est possible avec jQuery?

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

2023


origine


Réponses:


REMARQUE: Utilisation stopEventPropagation() est quelque chose qui devrait être évité car il brise le flux d'événements normal dans le DOM. Voir Cet article pour plus d'informations. Penser à utiliser cette méthode au lieu.

Attachez un événement click au corps du document qui ferme la fenêtre. Attachez un événement click distinct à la fenêtre qui arrête la propagation dans le corps du document.

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

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

1618



Vous pouvez écouter un Cliquez sur événement sur document puis assurez-vous #menucontainer n'est pas un ancêtre ou la cible de l'élément cliqué en utilisant .closest().

Si ce n'est pas le cas, l'élément cliqué est en dehors du #menucontainer et vous pouvez le cacher en toute sécurité.

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

Modifier - 2017-06-23

Vous pouvez également nettoyer après l'écouteur d'événement si vous envisagez de fermer le menu et que vous souhaitez arrêter d'écouter les événements. Cette fonction ne nettoie que l'écouteur nouvellement créé, en préservant tous les autres écouteurs de clic document. Avec la syntaxe ES2015:

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

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

  document.addEventListener('click', outsideClickListener)
}

Éditer - 2018-03-11

Pour ceux qui ne veulent pas utiliser jQuery. Voici le code ci-dessus dans plain vanillaJS (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target)) { // or use: event.target.closest(selector) === null
            if (isVisible(element)) {
                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 

REMARQUE: Ceci est basé sur le commentaire d'Alex à utiliser !element.contains(event.target) au lieu de la partie jQuery.

Mais element.closest() est maintenant également disponible dans tous les principaux navigateurs (la version W3C diffère un peu de celle de jQuery). Polyfills peut être trouvé ici: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest


1123



Comment détecter un clic en dehors d'un élément?

La raison pour laquelle cette question est si populaire et a tellement de réponses est qu'elle est trompeusement complexe. Après presque huit ans et des douzaines de réponses, je suis vraiment surpris de voir à quel point on s'est peu soucié de l'accessibilité.

Je voudrais cacher ces éléments lorsque l'utilisateur clique en dehors de la zone des menus.

C'est une cause noble et est le réel problème. Le titre de la question - qui est ce que la plupart des réponses semblent essayer de résoudre - contient une fausse piste.

Astuce: c'est le mot "Cliquez sur"!

Vous ne voulez pas réellement lier les gestionnaires de clic.

Si vous liez des gestionnaires de clics pour fermer la boîte de dialogue, vous avez déjà échoué. La raison pour laquelle vous avez échoué est que tout le monde ne se déclenche pas click événements. Les utilisateurs n'utilisant pas de souris pourront échapper à votre boîte de dialogue (et votre menu local est sans doute un type de boîte de dialogue) en appuyant sur Languette, et ils ne seront pas en mesure de lire le contenu derrière le dialogue sans déclencher par la suite click un événement.

Alors reformulons la question.

Comment ferme-t-on une boîte de dialogue quand un utilisateur en a fini?

C'est le but. Malheureusement, nous devons maintenant lier le userisfinishedwiththedialog événement, et que la liaison n'est pas si simple.

Alors, comment pouvons-nous détecter qu'un utilisateur a fini d'utiliser un dialogue?

focusout un événement

Un bon début consiste à déterminer si la mise au point a quitté le dialogue.

Astuce: faites attention à la blur un événement, blur ne se propage pas si l'événement était lié à la phase de bullage!

jQuery focusout fera très bien. Si vous ne pouvez pas utiliser jQuery, vous pouvez utiliser blur pendant la phase de capture:

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

En outre, pour de nombreuses boîtes de dialogue, vous devez autoriser le conteneur à se concentrer. Ajouter tabindex="-1" pour permettre au dialogue de recevoir le focus dynamiquement sans interrompre le flux de tabulation.

$('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 vous jouez avec cette démo pendant plus d'une minute, vous devriez rapidement commencer à voir les problèmes.

Le premier est que le lien dans la boîte de dialogue n'est pas cliquable. Si vous tentez de cliquer dessus ou d'un onglet, la boîte de dialogue se fermera avant que l'interaction ne se produise. C'est parce que la mise au point de l'élément interne déclenche une focusout événement avant de déclencher un focusin événement à nouveau.

Le correctif consiste à mettre en file d'attente le changement d'état sur la boucle d'événements. Cela peut être fait en utilisant setImmediate(...), ou setTimeout(..., 0) pour les navigateurs qui ne supportent pas setImmediate. Une fois en file d'attente, il peut être annulé par 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>

Le deuxième problème est que la boîte de dialogue ne se ferme pas lorsque le lien est à nouveau enfoncé. En effet, le dialogue perd le focus, ce qui déclenche le comportement de fermeture, après quoi le clic sur le lien déclenche la réouverture de la boîte de dialogue.

Comme dans le cas précédent, l'état de focus doit être géré. Étant donné que la modification d'état a déjà été mise en file d'attente, il s'agit simplement de gérer les événements de mise au point sur les déclencheurs de la boîte de dialogue:

Cela devrait sembler familier
$('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 clé

Si vous pensiez avoir terminé en gérant les états de mise au point, vous pouvez en faire plus pour simplifier l'expérience de l'utilisateur.

C'est souvent une fonctionnalité "agréable à avoir", mais il est commun que lorsque vous avez un modal ou un popup de toute sorte que le Esc la clé va le fermer.

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 vous savez que vous avez des éléments focalisables dans la boîte de dialogue, vous n'aurez pas besoin de faire le focus directement sur la boîte de dialogue. Si vous construisez un menu, vous pouvez vous concentrer sur le premier élément de menu à la place.

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.


WAI-ARIA Rôles et autres aides à l'accessibilité

Cette réponse couvre, je l'espère, les bases de la prise en charge du clavier et de la souris pour cette fonctionnalité, mais comme elle est déjà assez importante, je vais éviter toute discussion sur Les rôles et attributs WAI-ARIA, Cependant, je très recommande aux responsables de la mise en œuvre de se reporter à la spécification pour plus de détails sur les rôles qu'ils doivent utiliser et sur tout autre attribut approprié.


184



Les autres solutions ici n'ont pas fonctionné pour moi donc j'ai dû utiliser:

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

127



J'ai une application qui fonctionne de manière similaire à l'exemple d'Eran, sauf que j'attache l'événement click au corps quand j'ouvre le menu ... Un peu comme ceci:

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

  event.stopPropagation();
});

Plus d'informations sur jQuery one() fonction


118



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

Ca marche bien pour moi.


32



Maintenant il y a un plugin pour ça: événements extérieurs (article de blog)

Ce qui suit arrive quand un clickoutside gestionnaire (WLOG) est lié à un élément:

  • l'élément est ajouté à un tableau qui contient tous les éléments avec clickoutside manipulateurs
  • une (namespaced) Cliquez sur gestionnaire est lié au document (s'il n'y est pas déjà)
  • sur tout Cliquez sur dans le document, le clickoutside événement est déclenché pour les éléments de ce tableau qui ne sont pas égaux à ou un parent de la Cliquez sur-vise cible
  • En outre, le event.target pour le clickoutside l'événement est défini sur l'élément sur lequel l'utilisateur a cliqué (vous savez même ce que l'utilisateur a cliqué, pas seulement qu'il a cliqué à l'extérieur)

Donc, aucun événement n'est arrêté de propagation et supplémentaire Cliquez sur les gestionnaires peuvent être utilisés "au-dessus" de l'élément avec le manipulateur externe.


31



Cela a fonctionné pour moi parfaitement !!

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

26



Après recherche, j'ai trouvé trois solutions de travail (j'ai oublié les liens de la page pour référence)

Première solution

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

Deuxième solution

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

Troisième solution

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

24



Je ne pense pas que ce dont vous avez vraiment besoin est de fermer le menu lorsque l'utilisateur clique à l'extérieur; Ce qu'il vous faut, c'est que le menu se ferme lorsque l'utilisateur clique n'importe où sur la page. Si vous cliquez sur le menu, ou sur le menu, il devrait fermer à droite?

Ne trouvant aucune réponse satisfaisante ci-dessus m'a incité à écrire ce blog l'autre jour. Pour les plus pédantes, il y a un certain nombre de pièges à prendre en compte:

  1. Si vous associez un gestionnaire d'événements click à l'élément body au moment du clic, veillez à attendre le 2ème clic avant de fermer le menu et à découpler l'événement. Sinon, l'événement de clic qui a ouvert le menu se répercutera sur l'auditeur qui doit fermer le menu.
  2. Si vous utilisez event.stopPropogation () sur un événement click, aucun autre élément de votre page ne peut avoir une fonctionnalité "cliquer n'importe où pour fermer".
  3. Attacher indéfiniment un gestionnaire d'événements click à l'élément body n'est pas une solution performante
  4. La comparaison de la cible de l'événement et de ses parents avec le créateur du gestionnaire suppose que ce que vous voulez faire est de fermer le menu lorsque vous cliquez dessus, lorsque vous voulez vraiment le fermer lorsque vous cliquez n'importe où sur la page.
  5. L'écoute d'événements sur l'élément body rendra votre code plus fragile. Coiffer aussi innocent que cela le briserait: body { margin-left:auto; margin-right: auto; width:960px;}

22



Comme le dit un autre poster, il y a beaucoup de pièges, surtout si l'élément que vous affichez (dans ce cas un menu) a des éléments interactifs. J'ai trouvé la méthode suivante pour être assez robuste:

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

    })
});

21