J'ai quelques menus HTML, que je montre complètement quand un utilisateur clique sur la tête de ces menus. Je souhaite masquer ces éléments lorsque l'utilisateur clique en dehors de la zone des menus.
Est-ce que quelque chose comme ceci est possible avec jQuery?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
REMARQUE: l'utilisation de
stopEventPropagation()
doit être évitée car elle interrompt le flux d'événements normal dans le DOM. Voir cet article pour plus d'informations. Pensez à utiliser cette méthode à la place
Attachez un événement de clic au corps du document qui ferme la fenêtre. Attachez au conteneur un événement click distinct qui arrête la propagation vers le corps du document.
$(window).click(function() {
//Hide the menus if visible
});
$('#menucontainer').click(function(event){
event.stopPropagation();
});
Vous pouvez écouter un événement click sur document
, puis vous assurer que #menucontainer
n'est pas un ancêtre ni la cible de l'élément sur lequel vous avez cliqué en utilisant .closest()
.
Si ce n'est pas le cas, l'élément sur lequel vous avez cliqué est en dehors du #menucontainer
et vous pouvez le masquer en toute sécurité.
$(document).click(function(event) {
$target = $(event.target);
if(!$target.closest('#menucontainer').length &&
$('#menucontainer').is(":visible")) {
$('#menucontainer').hide();
}
});
Vous pouvez également nettoyer après l'écouteur d'événements si vous envisagez de fermer le menu et de ne plus écouter les événements. Cette fonction nettoie que le programme d'écoute nouvellement créé, en préservant les autres programmes d'écoute de clic sur document
. Avec la syntaxe ES2015:
export function hideOnClickOutside(selector) {
const outsideClickListener = (event) => {
$target = $(event.target);
if (!$target.closest(selector).length && $(selector).is(':visible')) {
$(selector).hide();
removeClickListener();
}
}
const removeClickListener = () => {
document.removeEventListener('click', outsideClickListener)
}
document.addEventListener('click', outsideClickListener)
}
Pour ceux qui ne veulent pas utiliser jQuery. Voici le code ci-dessus en clair 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
NOTE: Ceci est basé sur le commentaire Alex d’utiliser simplement !element.contains(event.target)
au lieu de la partie jQuery.
Mais element.closest()
est maintenant disponible dans tous les principaux navigateurs (la version du W3C est légèrement différente de celle de jQuery) . Polyfills est disponible ici: https://developer.mozilla.org/en-US/docs/ Web/API/Elément/le plus proche
Comment détecter un clic en dehors d'un élément?
La raison pour laquelle cette question est si populaire et a tant de réponses est qu’elle est trompeusement complexe. Après presque huit ans et des dizaines de réponses, je suis vraiment surpris de constater à quel point l’accessibilité a été négligée.
Je souhaite masquer ces éléments lorsque l'utilisateur clique en dehors de la zone des menus.
Ceci est une noble cause et constitue le problème actuel . Le titre de la question - ce que la plupart des réponses semblent tenter de résoudre - contient un malheur redoutable.
Astuce: c'est le mot "cliquez" !
Si vous liez des gestionnaires qui cliquent pour fermer la boîte de dialogue, vous avez déjà échoué. La raison de votre échec est que tout le monde ne déclenche pas les événements click
. Les utilisateurs qui n'utilisent pas de souris pourront échapper à votre boîte de dialogue (et votre menu contextuel est sans doute un type de boîte de dialogue) en appuyant sur Tabet ils ne pourront alors pas lire le contenu derrière la boîte de dialogue sans déclencher ultérieurement un événement click
.
Alors reformulons la question.
Comment ferme-t-on une boîte de dialogue lorsqu'un utilisateur en a fini?
C'est le but. Malheureusement, nous devons maintenant lier l'événement userisfinishedwiththedialog
, et cette liaison n'est pas si simple.
Alors, comment pouvons-nous détecter qu'un utilisateur a fini d'utiliser un dialogue?
focusout
événementUn bon début consiste à déterminer si le focus est sorti de la boîte de dialogue.
Astuce: soyez prudent avec l'événement blur
, blur
ne se propage pas si l'événement était lié à la phase de bullage!
jQuery's focusout
fera très bien l'affaire. Si vous ne pouvez pas utiliser jQuery, vous pouvez utiliser blur
pendant la phase de capture:
element.addEventListener('blur', ..., true);
// use capture: ^^^^
De plus, pour de nombreuses boîtes de dialogue, vous devez autoriser le conteneur à se concentrer. Ajoutez tabindex="-1"
pour permettre à la boîte de dialogue de recevoir le focus de manière dynamique 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.
La première est que le lien dans la boîte de dialogue n'est pas cliquable. Si vous tentez de cliquer dessus ou d'y accéder, la boîte de dialogue se fermera avant l'interaction. En effet, la focalisation de l'élément interne déclenche un événement focusout
avant de déclencher à nouveau un événement focusin
.
Le correctif consiste à mettre en file d'attente le changement d'état sur la boucle d'événement. Cela peut être fait en utilisant setImmediate(...)
ou setTimeout(..., 0)
pour les navigateurs qui ne prennent pas en charge setImmediate
. Une fois mis en file d'attente, il peut être annulé par un focusin
suivant:
$('.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 fermera pas lorsque le lien sera à nouveau appuyé. En effet, la boîte de dialogue perd le focus, ce qui déclenche le comportement de fermeture, après quoi le clic de lien déclenche la réouverture de la boîte de dialogue.
Comme dans le précédent numéro, l’état d’intervention doit être géré. Étant donné que le changement d'état a déjà été mis en file d'attente, il ne s'agit que de gérer les événements de focus sur les déclencheurs 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>
Si vous pensiez avoir terminé en gérant les états prioritaires, vous pouvez faire davantage pour simplifier l'expérience utilisateur.
Il s’agit souvent d’une fonctionnalité intéressante, mais il est courant que lorsque vous avez un modal ou un popup de quelque sorte que ce Esc la clé le fermera.
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 la boîte de dialogue contient des éléments activables, vous n'avez pas besoin de la centrer directement. Si vous construisez un menu, vous pouvez plutôt vous concentrer sur le premier élément de menu.
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.
Nous espérons que cette réponse couvre les bases de la prise en charge accessible du clavier et de la souris pour cette fonctionnalité, mais comme elle est déjà assez volumineuse, je vais éviter toute discussion sur rôles et attributs de WAI-ARIA , cependant je hautement recommande aux implémenteurs de se référer à la spécification pour savoir quels rôles ils doivent utiliser et tout autre attribut approprié.
Les autres solutions ici ne fonctionnaient pas pour moi alors je devais utiliser:
if(!$(event.target).is('#foo'))
{
// hide menu
}
J'ai une application qui fonctionne de la même manière que l'exemple d'Eran, sauf que j'attache l'événement click au corps lorsque 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 Fonction one()
de jQuery
$("#menuscontainer").click(function() {
$(this).focus();
});
$("#menuscontainer").blur(function(){
$(this).hide();
});
Ça marche très bien pour moi.
Maintenant, il existe un plugin pour cela: événements extérieurs ( blog post )
Ce qui suit se produit lorsqu'un gestionnaire clickoutside (WLOG) est lié à un élément:
Ainsi, aucun événement n'est arrêté et les gestionnaires click supplémentaires peuvent être utilisés "au-dessus" de l'élément avec le gestionnaire extérieur.
Après des recherches, j'ai trouvé trois solutions de travail (j'ai oublié les liens de page pour référence)
<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>
<script>
$('body').on('click', function(e) {
if($(e.target).closest('#testDiv').length == 0) {
/* Hide dropdown here */
}
});
</script>
<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>
Cela a fonctionné pour moi parfaitement !!
$('html').click(function (e) {
if (e.target.id == 'YOUR-DIV-ID') {
//do something
} else {
//do something
}
});
Je ne pense pas que ce dont vous avez vraiment besoin est de fermer le menu lorsque l'utilisateur clique à l'extérieur; vous avez besoin que le menu se ferme lorsque l'utilisateur clique n'importe où sur la page. Si vous cliquez sur le menu, ou en dehors du menu, il devrait fermer correctement?
Ne pas trouver de réponses satisfaisantes ci-dessus m'a incité à écrire cet article de blog l'autre jour. Pour les plus pédants, il y a plusieurs pièges à prendre en compte:
body { margin-left:auto; margin-right: auto; width:960px;}
Comme l'a dit une autre affiche, il y a beaucoup de pièges, en particulier si l'élément que vous affichez (dans ce cas un menu) a des éléments interactifs . J'ai trouvé la méthode suivante 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);
}
})
});
Une solution simple à la situation est la suivante:
$(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();
}
});
Le script ci-dessus masquera la div
si l'événement de clic div
est déclenché.
Vous pouvez consulter le blog suivant pour plus d'informations: http://www.codecanal.com/detect-click-outside-div-using-javascript/
Au lieu d'utiliser event.stopPropagation () qui peut avoir des effets secondaires, définissez simplement une variable indicateur et ajoutez une condition if
. J'ai testé cela et fonctionné correctement sans aucun effet secondaire 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";
}
});
Avec juste une simple condition if
:
$(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
}
});
Vérifiez la cible d'événement de clic de la fenêtre (elle devrait se propager à la fenêtre, tant qu'elle n'est pas capturée ailleurs), et assurez-vous que ce n'est pas l'un des éléments de menu. Si ce n'est pas le cas, vous êtes en dehors de votre menu.
Ou vérifiez la position du clic et voyez si elle est contenue dans la zone du menu.
J'ai eu du succès avec quelque chose comme ça:
var $menuscontainer = ...;
$('#trigger').click(function() {
$menuscontainer.show();
$('body').click(function(event) {
var $target = $(event.target);
if ($target.parents('#menuscontainer').length == 0) {
$menuscontainer.hide();
}
});
});
La logique est la suivante: lorsque #menuscontainer
est affiché, liez un gestionnaire de clic au corps qui masque #menuscontainer
uniquement si la cible (du clic) n'en est pas un enfant.
En 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();
}
});
Il n’ya aucun problème avec en stoppant la propagation d’événements et supporte mieux plusieurs menus sur la même page où cliquer sur un deuxième menu alors qu’un premier est ouvert laissera le premier ouvert dans la solution stopPropagation.
J'ai trouvé cette méthode dans un plugin d'agenda 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);
L'événement a une propriété appelée event.path de l'élément qui est une "liste ordonnée statique de tous ses ancêtres dans l'ordre de l'arbre" . Pour vérifier si un événement provient d'un élément DOM spécifique ou de l'un de ses enfants, il vous suffit de vérifier le chemin d'accès à cet élément DOM spécifique. Il peut également être utilisé pour vérifier plusieurs éléments en effectuant logiquement la transformation OR
de l'élément dans la fonction some
.
$("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;
}
<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>
Donc, pour votre cas, il devrait être
$("body").click(function() {
target = $("#menuscontainer")[0];
flag = event.path.some(function(el, i, arr) {
return (el == target)
});
if (!flag) {
// Hide the menus
}
});
Voici la solution JavaScript Vanilla pour les futurs téléspectateurs.
Lorsque vous cliquez sur un élément du document, si son ID est basculé ou si l'élément masqué n'est pas masqué et que l'élément masqué ne contient pas l'élément cliqué, basculez-le.
(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 vous allez avoir plusieurs bascules sur la même page, vous pouvez utiliser quelque chose comme ceci:
hidden
à l'élément compressible.(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>
Au lieu d'utiliser une interruption de flux, un événement de flou/focus ou toute autre technique délicate, associez simplement le flux d'événement à la parenté de l'élément:
$(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)
}
});
Pour supprimer un écouteur d'événements en dehors d'un clic, procédez comme suit:
$(document).off("click.menu-outside");
Si vous utilisez des scripts pour IE et FF 3. * et que vous souhaitez simplement savoir si le clic a eu lieu dans une zone donnée, vous pouvez également utiliser les éléments suivants:
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;}
Utilisation:
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();
});
Je suis surpris que personne n'ait reconnu l'événement focusout
:
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>
Accrochez un écouteur d'événement de clic sur le document. Dans l'écouteur d'événements, vous pouvez consulter l'objet événement , en particulier le event.target pour voir sur quel élément a été cliqué:
$(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");
}
});
$(document).click(function() {
$(".overlay-window").hide();
});
$(".overlay-window").click(function() {
return false;
});
Si vous cliquez sur le document, masquez un élément donné, sauf si vous cliquez sur ce même élément.
Pour une utilisation plus facile et un code plus expressif, j'ai créé un plugin jQuery pour cela:
$('div.my-element').clickOut(function(target) {
//do something here...
});
Remarque: target est l'élément sur lequel l'utilisateur a réellement cliqué. Mais le rappel est toujours exécuté dans le contexte de l'élément d'origine, vous pouvez donc utiliser this comme vous le souhaitez dans un rappel jQuery.
Brancher:
$.fn.clickOut = function (parent, fn) {
var context = this;
fn = (typeof parent === 'function') ? parent : fn;
parent = (parent instanceof jQuery) ? parent : $(document);
context.each(function () {
var that = this;
parent.on('click', function (e) {
var clicked = $(e.target);
if (!clicked.is(that) && !clicked.parents().is(that)) {
if (typeof fn === 'function') {
fn.call(that, clicked);
}
}
});
});
return context;
};
Par défaut, l'écouteur d'événements au clic est placé sur le document. Toutefois, si vous souhaitez limiter la portée de l'écouteur d'événements, vous pouvez transmettre un objet jQuery représentant un élément de niveau parent qui sera le premier parent auquel les clics seront écoutés. Cela évite les écouteurs d'événements de niveau document inutiles. Évidemment, cela ne fonctionnera que si l'élément parent fourni est un parent de votre élément initial.
Utilisez comme si:
$('div.my-element').clickOut($('div.my-parent'), function(target) {
//do something here...
});
Je l'ai fait comme ça dans YUI 3:
// Detect the click anywhere other than the overlay element to close it.
Y.one(document).on('click', function (e) {
if (e.target.ancestor('#overlay') === null && e.target.get('id') != 'show' && overlay.get('visible') == true) {
overlay.hide();
}
});
Je vérifie si ancêtre n'est pas le conteneur d'éléments de widget,
si la cible n’est pas celle qui ouvre le widget/élément,
Si le widget/élément que je veux fermer est déjà ouvert (pas si important).
Upvote pour la réponse la plus populaire, mais ajoutez
&& (e.target != $('html').get(0)) // ignore the scrollbar
un clic sur une barre de défilement ne permet donc pas de masquer ou de masquer l'élément cible.
Cela devrait fonctionner:
$('body').click(function (event) {
var obj = $(event.target);
obj = obj['context']; // context : clicked element inside body
if ($(obj).attr('id') != "menuscontainer" && $('#menuscontainer').is(':visible') == true) {
//hide menu
}
});
Une fonction:
$(function() {
$.fn.click_inout = function(clickin_handler, clickout_handler) {
var item = this;
var is_me = false;
item.click(function(event) {
clickin_handler(event);
is_me = true;
});
$(document).click(function(event) {
if (is_me) {
is_me = false;
} else {
clickout_handler(event);
}
});
return this;
}
});
Usage:
this.input = $('<input>')
.click_inout(
function(event) { me.ShowTree(event); },
function() { me.Hide(); }
)
.appendTo(this.node);
Et les fonctions sont très simples:
ShowTree: function(event) {
this.data_span.show();
}
Hide: function() {
this.data_span.hide();
}
J'ai utilisé le script ci-dessous et fait avec 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
}
})
Ci-dessous, trouvez le code 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>
Vous pouvez lire le tutoriel ici
Ceci est ma solution à ce problème:
$(document).ready(function() {
$('#user-toggle').click(function(e) {
$('#user-nav').toggle();
e.stopPropagation();
});
$('body').click(function() {
$('#user-nav').hide();
});
$('#user-nav').click(function(e){
e.stopPropagation();
});
});
J'ai fini par faire quelque chose comme ça:
$(document).on('click', 'body, #msg_count_results .close',function() {
$(document).find('#msg_count_results').remove();
});
$(document).on('click','#msg_count_results',function(e) {
e.preventDefault();
return false;
});
J'ai un bouton de fermeture dans le nouveau conteneur à des fins d'interface utilisateur conviviale. J'ai dû utiliser return false pour ne pas passer par. Bien sûr, avoir un HREF là-bas pour vous emmener quelque part serait bien, ou vous pourriez appeler des trucs ajax à la place. De toute façon, ça marche pour moi. Juste ce que je voulais.
Nous avons mis en œuvre une solution, basée en partie sur les commentaires d’un utilisateur ci-dessus, qui fonctionne parfaitement pour nous. Nous l'utilisons pour masquer un champ de recherche/les résultats lorsque vous cliquez en dehors de ces éléments, à l'exclusion de l'élément d'origine.
// HIDE SEARCH BOX IF CLICKING OUTSIDE
$(document).click(function(event){
// IF NOT CLICKING THE SEARCH BOX OR ITS CONTENTS OR SEARCH ICON
if ($("#search-holder").is(":visible") && !$(event.target).is("#search-holder *, #search")) {
$("#search-holder").fadeOut('fast');
$("#search").removeClass('active');
}
});
Il vérifie également si le champ de recherche est déjà visible en premier, et dans notre cas, il supprime également une classe active sur le bouton de recherche masquer/afficher.
Les solutions ici fonctionnent bien lorsqu'un seul élément doit être géré. Cependant, s’il existe plusieurs éléments, le problème est beaucoup plus compliqué. Les astuces avec e.stopPropagation () et tous les autres ne fonctionneront pas.
Je suis arrivé avec une solution, et peut-être que ce n'est pas si facile, mais c'est mieux que rien. Regarde:
$view.on("click", function(e) {
if(model.isActivated()) return;
var watchUnclick = function() {
rootView.one("mouseleave", function() {
$(document).one("click", function() {
model.deactivate();
});
rootView.one("mouseenter", function() {
watchUnclick();
});
});
};
watchUnclick();
model.activate();
});
Voici ce que je fais pour résoudre le problème.
$(window).click(function (event) {
//To improve performance add a checklike
//if(myElement.isClosed) return;
var isClickedElementChildOfMyBox = isChildOfElement(event,'#id-of-my-element');
if (isClickedElementChildOfMyBox)
return;
//your code to hide the element
});
var isChildOfElement = function (event, selector) {
if (event.originalEvent.path) {
return event.originalEvent.path[0].closest(selector) !== null;
}
return event.originalEvent.originalTarget.closest(selector) !== null;
}
Si quelqu'un de curieux, voici une solution javascript (es6):
window.addEventListener('mouseup', e => {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
yourDiv.classList.remove('show-menu');
//or yourDiv.style.display = 'none';
}
})
et es5, juste au cas où:
window.addEventListener('mouseup', function (e) {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
yourDiv.classList.remove('show-menu');
//or yourDiv.style.display = 'none';
}
});
Voici une solution simple en javascript pur. Il est mis à jour avec 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;
})
Ça marche pour moi
$("body").mouseup(function(e) {
var subject = $(".main-menu");
if(e.target.id != subject.attr('id') && !subject.has(e.target).length) {
$('.sub-menu').hide();
}
});
$(document).on("click",function (event)
{
console.log(event);
if ($(event.target).closest('.element').length == 0)
{
//your code here
if ($(".element").hasClass("active"))
{
$(".element").removeClass("active");
}
}
});
Essayez ce codage pour obtenir la solution.
Voici mon code:
// Listen to every click
$('html').click(function(event) {
if ( $('#mypopupmenu').is(':visible') ) {
if (event.target.id != 'click_this_to_show_mypopupmenu') {
$('#mypopupmenu').hide();
}
}
});
// Listen to selector's clicks
$('#click_this_to_show_mypopupmenu').click(function() {
// If the menu is visible, and you clicked the selector again we need to hide
if ( $('#mypopupmenu').is(':visible') {
$('#mypopupmenu').hide();
return true;
}
// Else we need to show the popup menu
$('#mypopupmenu').show();
});
Une autre solution est ici:
Usage:
<div onClick="$('#menu').toggle();$('#menu').clickOutside(function() { $(this).hide(); $(this).clickOutside('disable'); });">Open / Close Menu</div>
<div id="menu" style="display: none; border: 1px solid #000000; background: #660000;">I am a menu, whoa is me.</div>
Brancher:
(function($) {
var clickOutsideElements = [];
var clickListener = false;
$.fn.clickOutside = function(options, ignoreFirstClick) {
var that = this;
if (ignoreFirstClick == null) ignoreFirstClick = true;
if (options != "disable") {
for (var i in clickOutsideElements) {
if (clickOutsideElements[i].element[0] == $(this)[0]) return this;
}
clickOutsideElements.Push({ element : this, clickDetected : ignoreFirstClick, fnc : (typeof(options) != "function") ? function() {} : options });
$(this).on("click.clickOutside", function(event) {
for (var i in clickOutsideElements) {
if (clickOutsideElements[i].element[0] == $(this)[0]) {
clickOutsideElements[i].clickDetected = true;
}
}
});
if (!clickListener) {
if (options != null && typeof(options) == "function") {
$('html').click(function() {
for (var i in clickOutsideElements) {
if (!clickOutsideElements[i].clickDetected) {
clickOutsideElements[i].fnc.call(that);
}
if (clickOutsideElements[i] != null) clickOutsideElements[i].clickDetected = false;
}
});
clickListener = true;
}
}
}
else {
$(this).off("click.clickoutside");
for (var i = 0; i < clickOutsideElements.length; ++i) {
if (clickOutsideElements[i].element[0] == $(this)[0]) {
clickOutsideElements.splice(i, 1);
}
}
}
return this;
}
})(jQuery);
Si vous utilisez des outils tels que "Pop-up", vous pouvez utiliser l'événement "onFocusOut".
window.onload=function(){
document.getElementById("inside-div").focus();
}
function loseFocus(){
alert("Clicked outside");
}
#container{
background-color:lightblue;
width:200px;
height:200px;
}
#inside-div{
background-color:lightgray;
width:100px;
height:100px;
}
<div id="container">
<input type="text" id="inside-div" onfocusout="loseFocus()">
</div>
Je veux juste que la réponse de @Pistos soit plus claire car elle est cachée dans les commentaires.
Cette solution a parfaitement fonctionné pour moi. JS simple:
var elementToToggle = $('.some-element');
$(document).click( function(event) {
if( $(event.target).closest(elementToToggle).length === 0 ) {
elementToToggle.hide();
}
});
dans CoffeeScript:
elementToToggle = $('.some-element')
$(document).click (event) ->
if $(event.target).closest(elementToToggle).length == 0
elementToToggle.hide()
Cela a parfaitement fonctionné à temps pour moi:
$('body').click(function() {
// Hide the menus if visible.
});
Je sais qu'il y a un million de réponses à cette question, mais j'ai toujours été fan de HTML et de CSS pour effectuer la majeure partie du travail. Dans ce cas, z-index et positionnement. La manière la plus simple que j'ai trouvée de faire ceci est la suivante:
$("#show-trigger").click(function(){
$("#element").animate({width: 'toggle'});
$("#outside-element").show();
});
$("#outside-element").click(function(){
$("#element").hide();
$("#outside-element").hide();
});
#outside-element {
position:fixed;
width:100%;
height:100%;
z-index:1;
display:none;
}
#element {
display:none;
padding:20px;
background-color:#ccc;
width:300px;
z-index:2;
position:relative;
}
#show-trigger {
padding:20px;
background-color:#ccc;
margin:20px auto;
z-index:2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outside-element"></div>
<div id="element">
<div class="menu-item"><a href="#1">Menu Item 1</a></div>
<div class="menu-item"><a href="#2">Menu Item 1</a></div>
<div class="menu-item"><a href="#3">Menu Item 1</a></div>
<div class="menu-item"><a href="#4">Menu Item 1</a></div>
</div>
<div id="show-trigger">Show Menu</div>
Cela crée un environnement sûr, car rien ne sera déclenché à moins que le menu ne soit réellement ouvert et que l'index z empêche tout contenu de l'élément de créer des ratés lors du clic.
De plus, vous n'avez pas besoin de jQuery pour couvrir toutes vos bases avec des appels de propagation et pour purger tous les éléments internes des ratés.
La réponse marquée comme réponse acceptée ne prend pas en compte le fait que vous pouvez avoir des superpositions sur l'élément, telles que des boîtes de dialogue, des popovers, des sélecteurs de date, etc.
J'ai créé ma propre version qui en tient compte. Il est créé en tant que KnockoutJS binding, mais il peut facilement être converti en jQuery uniquement.
Cela fonctionne à partir de la première requête pour tous les éléments visibles avec une position absolue ou une position absolue. Il frappe ensuite les éléments en question par rapport à ceux que je souhaite masquer si vous cliquez à l'extérieur. Si c'est un succès, je calcule un nouveau rectangle lié qui prend en compte les limites de superposition.
ko.bindingHandlers.clickedIn = (function () {
function getBounds(element) {
var pos = element.offset();
return {
x: pos.left,
x2: pos.left + element.outerWidth(),
y: pos.top,
y2: pos.top + element.outerHeight()
};
}
function hitTest(o, l) {
function getOffset(o) {
for (var r = { l: o.offsetLeft, t: o.offsetTop, r: o.offsetWidth, b: o.offsetHeight };
o = o.offsetParent; r.l += o.offsetLeft, r.t += o.offsetTop);
return r.r += r.l, r.b += r.t, r;
}
for (var b, s, r = [], a = getOffset(o), j = isNaN(l.length), i = (j ? l = [l] : l).length; i;
b = getOffset(l[--i]), (a.l == b.l || (a.l > b.l ? a.l <= b.r : b.l <= a.r))
&& (a.t == b.t || (a.t > b.t ? a.t <= b.b : b.t <= a.b)) && (r[r.length] = l[i]));
return j ? !!r.length : r;
}
return {
init: function (element, valueAccessor) {
var target = valueAccessor();
$(document).click(function (e) {
if (element._clickedInElementShowing === false && target()) {
var $element = $(element);
var bounds = getBounds($element);
var possibleOverlays = $("[style*=z-index],[style*=absolute]").not(":hidden");
$.each(possibleOverlays, function () {
if (hitTest(element, this)) {
var b = getBounds($(this));
bounds.x = Math.min(bounds.x, b.x);
bounds.x2 = Math.max(bounds.x2, b.x2);
bounds.y = Math.min(bounds.y, b.y);
bounds.y2 = Math.max(bounds.y2, b.y2);
}
});
if (e.clientX < bounds.x || e.clientX > bounds.x2 ||
e.clientY < bounds.y || e.clientY > bounds.y2) {
target(false);
}
}
element._clickedInElementShowing = false;
});
$(element).click(function (e) {
e.stopPropagation();
});
},
update: function (element, valueAccessor) {
var showing = ko.utils.unwrapObservable(valueAccessor());
if (showing) {
element._clickedInElementShowing = true;
}
}
};
})();
Pour être honnête, je n’ai aimé aucune des solutions précédentes.
La meilleure façon de le faire est de lier l'événement "click" au document et de comparer si ce clic est vraiment en dehors de l'élément (tout comme Art l'a dit dans sa suggestion).
Cependant, vous rencontrerez quelques problèmes: vous ne pourrez jamais le dissocier et vous ne pouvez pas avoir de bouton externe pour ouvrir/fermer cet élément.
C'est pourquoi j'ai écrit ce petit plugin (cliquez ici pour le lien) , pour simplifier ces tâches. Pourrait-il être plus simple?
<a id='theButton' href="#">Toggle the menu</a><br/>
<div id='theMenu'>
I should be toggled when the above menu is clicked,
and hidden when user clicks outside.
</div>
<script>
$('#theButton').click(function(){
$('#theMenu').slideDown();
});
$("#theMenu").dClickOutside({ ignoreList: $("#theButton") }, function(clickedObj){
$(this).slideUp();
});
</script>
Pour ce faire, la méthode la plus large consiste à tout sélectionner sur la page Web, à l'exception de l'élément pour lequel vous ne souhaitez pas que les clics soient détectés, et à lier l'événement click à l'ouverture du menu.
Ensuite, lorsque le menu est fermé, supprimez la liaison.
Utilisez .stopPropagation pour éviter que l'événement n'affecte une partie quelconque du menuscontainer.
$("*").not($("#menuscontainer")).bind("click.OutsideMenus", function ()
{
// hide the menus
//then remove all of the handlers
$("*").unbind(".OutsideMenus");
});
$("#menuscontainer").bind("click.OutsideMenus", function (event)
{
event.stopPropagation();
});
jQuery().ready(function(){
$('#nav').click(function (event) {
$(this).addClass('activ');
event.stopPropagation();
});
$('html').click(function () {
if( $('#nav').hasClass('activ') ){
$('#nav').removeClass('activ');
}
});
});
Pour les appareils tactiles comme iPad et iPhone, nous pouvons utiliser ce code:
$(document).on('touchstart', function (event) {
var container = $("YOUR CONTAINER SELECTOR");
if (!container.is(e.target) && // If the target of the click isn't the container...
container.has(e.target).length === 0) // ... nor a descendant of the container
{
container.hide();
}
});
Cela pourrait être une meilleure solution pour certaines personnes.
$(".menu_link").click(function(){
// show menu code
});
$(".menu_link").mouseleave(function(){
//hide menu code, you may add a timer for 3 seconds before code to be run
});
Je sais que mouseleave ne signifie pas seulement un clic à l'extérieur, mais aussi que vous quittez la zone de cet élément.
Une fois que le menu lui-même est à l'intérieur de l'élément menu_link
, le menu lui-même ne devrait pas poser de problème pour cliquer ou pour continuer.
$('#propertyType').on("click",function(e){
self.propertyTypeDialog = !self.propertyTypeDialog;
b = true;
e.stopPropagation();
console.log("input clicked");
});
$(document).on('click','body:not(#propertyType)',function (e) {
e.stopPropagation();
if(b == true) {
if ($(e.target).closest("#configuration").length == 0) {
b = false;
self.propertyTypeDialog = false;
console.log("outside clicked");
}
}
// console.log($(e.target).closest("#configuration").length);
});
Je crois que la meilleure façon de le faire est quelque chose comme ça.
$(document).on("click", function(event) {
clickedtarget = $(event.target).closest('#menuscontainer');
$("#menuscontainer").not(clickedtarget).hide();
});
Ce type de solution pourrait facilement être utilisé pour plusieurs menus, ainsi que pour les menus ajoutés dynamiquement via javascript. En gros, il vous permet simplement de cliquer n'importe où dans votre document, de vérifier l'élément sur lequel vous avez cliqué et de sélectionner son "#menuscontainer" le plus proche. Ensuite, tous les ménusconteneurs sont masqués, à l’exclusion de celui sur lequel vous avez cliqué.
Vous ne savez pas exactement comment vos menus sont construits, mais n'hésitez pas à copier mon code dans le JSFiddle. C'est un système menu/modal très simple mais parfaitement fonctionnel. Tout ce que vous avez à faire est de construire les menus HTML et le code fera le travail pour vous.
Pour masquer fileTreeClass
si l'utilisateur clique dessus
jQuery(document).mouseup(function (e) {
var container = $(".fileTreeClass");
if (!container.is(e.target) // if the target of the click isn't the container...
&& container.has(e.target).length === 0) // ... nor a descendant of the container
{
container.hide();
}
});
Plugin simple:
$.fn.clickOff = function(callback, selfDestroy) {
var clicked = false;
var parent = this;
var destroy = selfDestroy || true;
parent.click(function() {
clicked = true;
});
$(document).click(function(event) {
if (!clicked && parent.is(':visible')) {
if(callback) callback.call(parent, event)
}
if (destroy) {
//parent.clickOff = function() {};
//parent.off("click");
//$(document).off("click");
parent.off("clickOff");
}
clicked = false;
});
};
Utilisation:
$("#myDiv").clickOff(function() {
alert('clickOff');
});
si vous souhaitez simplement afficher une fenêtre lorsque vous cliquez sur un bouton et que cette fenêtre est décompressée lorsque vous cliquez à l'extérieur (ou à nouveau sur le bouton), cela fonctionne bien.
document.body.onclick = function() { undisp_menu(); };
var menu_on = 0;
function menu_trigger(event){
if (menu_on == 0)
{
// otherwise u will call the undisp on body when
// click on the button
event.stopPropagation();
disp_menu();
}
else{
undisp_menu();
}
}
function disp_menu(){
menu_on = 1;
var e = document.getElementsByClassName("menu")[0];
e.className = "menu on";
}
function undisp_menu(){
menu_on = 0;
var e = document.getElementsByClassName("menu")[0];
e.className = "menu";
}
ne l'oubliez pas pour le bouton
<div class="button" onclick="menu_trigger(event)">
<div class="menu">
et le css:
.menu{
display: none;
}
.on {
display: inline-block;
}
Essayez ceci:
$('html').click(function(e) {
if($(e.target).parents('#menuscontainer').length == 0) {
$('#menuscontainer').hide();
}
});
https://jsfiddle.net/4cj4jxy0/
Notez cependant que cela ne peut pas fonctionner si l'événement click ne peut pas atteindre la balise html
. (Peut-être que d'autres éléments ont stopPropagation()
).
Juste un avertissement qui utilise ceci:
$('html').click(function() {
// Hide the menus if visible
});
$('#menucontainer').click(function(event){
event.stopPropagation();
});
Il empêche le pilote Ruby on Rails UJS de fonctionner correctement. Par exemple, link_to 'click', '/url', :method => :delete
ne fonctionnera pas.
Cela pourrait être une solution de contournement:
$('html').click(function() {
// Hide the menus if visible
});
$('#menucontainer').click(function(event){
if (!$(event.target).data('method')) {
event.stopPropagation();
}
});
$('html').click(function() {
//Hide the menus if visible
});
$('#menucontainer').click(function(event){
event.stopPropagation();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<button id='#menucontainer'>Ok</button>
</html>
Souscrire phase de capture de clic pour gérer cliquez sur les éléments qui appellent preventDefault
.
Recouvrez-le sur l'élément du document en utilisant l'autre nom click-anywhere
.
document.addEventListener('click', function (event) {
event = $.event.fix(event);
event.type = 'click-anywhere';
$document.trigger(event);
}, true);
Ensuite, là où vous avez besoin de fonctionnalités extérieures au clic, abonnez-vous à l'événement click-anywhere
sur document
et vérifiez si le clic s'est déroulé en dehors de l'élément qui vous intéresse:
$(document).on('click-anywhere', function (event) {
if (!$(event.target).closest('#smth').length) {
// Do anything you need here
}
});
Quelques notes:
Vous devez utiliser document
, car ce serait une faute de performance de déclencher un événement sur tous les éléments en dehors desquels le clic est survenu.
Cette fonctionnalité peut être encapsulée dans un plugin spécial, qui appelle un rappel sur un clic extérieur.
Vous ne pouvez pas vous abonner à la phase de capture en utilisant jQuery lui-même.
Vous n'avez pas besoin du chargement de document pour vous abonner car l'abonnement est sur document
, même pas sur sa body
, de sorte qu'il existe toujours indépendamment de l'emplacement du script et de l'état du chargement.
Supposons que la div que vous voulez détecter si l'utilisateur qui a cliqué à l'extérieur ou à l'intérieur a un identifiant, par exemple: "my-special-widget".
Écoutez les événements de clic corporel:
document.body.addEventListener('click', (e) => {
if (isInsideMySpecialWidget(e.target, "my-special-widget")) {
console.log("user clicked INSIDE the widget");
}
console.log("user clicked OUTSIDE the widget");
});
function isInsideMySpecialWidget(elem, mySpecialWidgetId){
while (elem.parentElement) {
if (elem.id === mySpecialWidgetId) {
return true;
}
elem = elem.parentElement;
}
return false;
}
Dans ce cas, vous n'interrompez pas le flux normal de clic sur un élément de votre page, car vous n'utilisez pas la méthode "stopPropagation".
$(document).on('click.menu.hide', function(e){
if ( !$(e.target).closest('#my_menu').length ) {
$('#my_menu').find('ul').toggleClass('active', false);
}
});
$(document).on('click.menu.show', '#my_menu li', function(e){
$(this).find('ul').toggleClass('active');
});
div {
float: left;
}
ul {
padding: 0;
position: relative;
}
ul li {
padding: 5px 25px 5px 10px;
border: 1px solid silver;
cursor: pointer;
list-style: none;
margin-top: -1px;
white-space: nowrap;
}
ul li ul:before {
margin-right: -20px;
position: absolute;
top: -17px;
right: 0;
content: "\25BC";
}
ul li ul li {
visibility: hidden;
height: 0;
padding-top: 0;
padding-bottom: 0;
border-width: 0 0 1px 0;
}
ul li ul li:last-child {
border: none;
}
ul li ul.active:before {
content: "\25B2";
}
ul li ul.active li {
display: list-item;
visibility: visible;
height: inherit;
padding: 5px 25px 5px 10px;
}
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<div>
<ul id="my_menu">
<li>Menu 1
<ul>
<li>subMenu 1</li>
<li>subMenu 2</li>
<li>subMenu 3</li>
<li>subMenu 4</li>
</ul>
</li>
<li>Menu 2
<ul>
<li>subMenu 1</li>
<li>subMenu 2</li>
<li>subMenu 3</li>
<li>subMenu 4</li>
</ul>
</li>
<li>Menu 3</li>
<li>Menu 4</li>
<li>Menu 5</li>
<li>Menu 6</li>
</ul>
</div>
Voici la version de jsbin http://jsbin.com/xopacadeni/edit?html,css,js,output
$('#menucontainer').click(function(e){
e.stopPropagation();
});
$(document).on('click', function(e){
// code
});
Cela fera basculer le menu de navigation lorsque vous cliquez sur l’élément.
$(document).on('click', function(e) {
var elem = $(e.target).closest('#menu'),
box = $(e.target).closest('#nav');
if (elem.length) {
e.preventDefault();
$('#nav').toggle();
} else if (!box.length) {
$('#nav').hide();
}
});
<li id="menu"><a></a></li>
<ul id="nav" > //Nav will toggle when you Click on Menu(it can be an icon in this example)
<li class="page"><a>Page1</a></li>
<li class="page"><a>Pag2</a></li>
<li class="page"><a>Page3</a></li>
<li class="page"><a>Page4</a></li>
</ul>
Il s'agit d'une solution plus générale qui permet de surveiller plusieurs éléments et d'ajouter et de supprimer dynamiquement des éléments de la file d'attente.
Il contient une file d'attente globale (autoCloseQueue) - un conteneur d'objets pour les éléments devant être fermés en dehors des clics.
Chaque clé d'objet de file d'attente doit être l'ID d'élément DOM et la valeur doit être un objet avec 2 fonctions de rappel:
{onPress: someCallbackFunction, onOutsidePress: anotherCallbackFunction}
Mettez ceci dans votre rappel de document prêt:
window.autoCloseQueue = {}
$(document).click(function(event) {
for (id in autoCloseQueue){
var element = autoCloseQueue[id];
if ( ($(e.target).parents('#' + id).length) > 0) { // This is a click on the element (or its child element)
console.log('This is a click on an element (or its child element) with id: ' + id);
if (typeof element.onPress == 'function') element.onPress(event, id);
} else { //This is a click outside the element
console.log('This is a click outside the element with id: ' + id);
if (typeof element.onOutsidePress == 'function') element.onOutsidePress(event, id); //call the outside callback
delete autoCloseQueue[id]; //remove the element from the queue
}
}
});
Ensuite, lorsque l'élément DOM portant l'id 'menuscontainer' est créé, ajoutez simplement cet objet à la file d'attente:
window.autoCloseQueue['menuscontainer'] = {onOutsidePress: clickOutsideThisElement}