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

2488

J'ai quelques menus HTML, que je montre complètement lorsqu'un utilisateur clique sur la tête de ces menus. Je voudrais masquer 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
});
Sergio del Amo
la source
47
Voici un exemple de cette stratégie: jsfiddle.net/tedp/aL7Xe/1
Ted
18
Comme Tom l'a mentionné, vous voudrez lire css-tricks.com/dangers-stopping-event-propagation avant d'utiliser cette approche. Cet outil jsfiddle est plutôt cool.
Jon Coombs
3
obtenir une référence à l'élément, puis event.target, et enfin! = ou == les deux, puis exécuter le code en conséquence ..
Rohit Kumar
Je recommande d'utiliser github.com/gsantiago/jquery-clickout :)
Rômulo M. Farias
2
Solution Vanilla JS avec event.targetet sans event.stopPropagation .
lowtechsun

Réponses:

1813

REMARQUE: L'utilisation stopEventPropagation()est quelque chose qui doit être évité car il rompt le flux d'événements normal dans le DOM. Consultez cet article pour plus d'informations. Envisagez plutôt d' utiliser cette méthode

Attachez un événement de clic au corps du document qui ferme la fenêtre. Attachez un événement de clic distinct au conteneur qui arrête la propagation vers le corps du document.

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

$('#menucontainer').click(function(event){
    event.stopPropagation();
});
Eran Galperin
la source
708
Cela casse le comportement standard de nombreuses choses, y compris les boutons et les liens, contenus dans #menucontainer. Je suis surpris que cette réponse soit si populaire.
Art
75
Cela ne rompt pas le comportement de quoi que ce soit à l'intérieur de #menucontainer, car il se trouve au bas de la chaîne de propagation pour tout ce qui s'y trouve.
Eran Galperin
94
c'est très beau mais vous ne devez $('html').click()pas utiliser le corps. Le corps a toujours la hauteur de son contenu. Il n'y a pas beaucoup de contenu ou l'écran est très haut, cela ne fonctionne que sur la partie remplie par le corps.
meo
103
Je suis également surpris que cette solution ait obtenu autant de votes. Cela échouera pour tout élément extérieur qui a stopPropagation jsfiddle.net/Flandre/vaNFw/3
Andre
140
Philip Walton explique très bien pourquoi cette réponse n'est pas la meilleure solution: css-tricks.com/dangers-stopping-event-propagation
Tom
1387

Vous pouvez écouter un événement de clic sur documentpuis vous assurer qu'il #menucontainern'est pas un ancêtre ou la cible de l'élément cliqué à l'aide de .closest().

Si ce n'est pas le cas, l'élément cliqué est en dehors de #menucontaineret 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();
  }        
});

Éditer - 23/06/2017

Vous pouvez également nettoyer après l'écouteur d'événements si vous prévoyez de fermer le menu et que vous souhaitez arrêter d'écouter les événements. Cette fonction nettoiera uniquement 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) => {
    $target = $(event.target);
    if (!$target.closest(selector).length && $(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) && 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 

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

Mais element.closest()est désormais également disponible dans tous les principaux navigateurs (la version W3C diffère un peu de celle de jQuery). Les polyfills peuvent être trouvés ici: Element.closest ()

Éditer - 2020-05-21

Dans le cas où vous souhaitez que l'utilisateur puisse cliquer et faire glisser à l'intérieur de l'élément, puis relâchez la souris à l'extérieur de l'élément, sans fermer l'élément:

      ...
      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);

Et dans 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()
        }
    }
Art
la source
28
J'ai essayé beaucoup d'autres réponses, mais seule celle-ci a fonctionné. Merci. Le code que j'ai fini par utiliser était le suivant: $ (document) .click (function (event) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('rapide');}});
Pistos
39
En fait, j'ai fini par utiliser cette solution car elle prend mieux en charge plusieurs menus sur la même page, où en cliquant sur un deuxième menu alors qu'un premier est ouvert, le premier sera ouvert dans la solution stopPropagation.
umassthrower
13
Excellente réponse. C'est la voie à suivre lorsque vous avez plusieurs éléments que vous souhaitez fermer.
John
5
Cela devrait être la réponse acceptée en raison de la faille de l'autre solution avec event.stopPropagation ().
tomate
20
Sans jQuery - en !element.contains(event.target)utilisant Node.contains ()
Alex Ross
305

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 d'une complexité trompeuse. Après près de huit ans et des dizaines de réponses, je suis véritablement surpris de voir le peu de soin accordé à l'accessibilité.

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

C'est une noble cause et c'est le vrai problème. Le titre de la question - qui est ce à quoi la plupart des réponses semblent tenter de répondre - contient un triste hareng rouge.

Astuce: c'est le mot "clic" !

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 déclenche pas d' clickévénements. Les utilisateurs n'utilisant 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 Tab, et ils ne pourront alors pas lire le contenu derrière la boîte de dialogue sans déclencher par la suite unclick événement.

Alors reformulons la question.

Comment ferme-t-on une boîte de dialogue lorsqu'un utilisateur en a terminé avec elle?

Tel est l'objectif. Malheureusement, nous devons maintenant lieruserisfinishedwiththedialog événement, et cette liaison n'est pas si simple.

Alors, comment pouvons-nous détecter qu'un utilisateur a fini d'utiliser une boîte de dialogue?

focusout un événement

Un bon début consiste à déterminer si le focus a quitté la boîte de dialogue.

Astuce: soyez prudent avec l' blurévénement, blurne se propage pas si l'événement était lié à la phase bouillonnante!

jQuery focusoutfera très bien. Si vous ne pouvez pas utiliser jQuery, vous pouvez utiliser blurpendant la phase de capture:

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

De plus, pour de nombreuses boîtes de dialogue, vous devrez permettre au conteneur de 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 des problèmes.

Le premier est que le lien dans la boîte de dialogue n'est pas cliquable. Tenter de cliquer dessus ou sur un onglet entraînera la fermeture de la boîte de dialogue avant que l'interaction n'ait lieu. En effet, la focalisation de l'élément interne déclenche un focusoutévénement avant de déclencher unefocusin nouveau événement.

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 prennent pas en charge setImmediate. Une fois mis en file d'attente, il peut être annulé par un autre 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'));
  }
});

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

Comme pour le numéro précédent, l'état de mise au point doit être géré. Étant donné que le changement d'état a déjà été mis en file d'attente, il s'agit simplement de gérer les événements de focus sur les déclencheurs de dialogue:

Cela devrait vous 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'));  
  }
});


Esc clé

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

C'est souvent une fonctionnalité "agréable à avoir", mais il est courant que lorsque vous avez un modal ou une popup de quelque sorte que ce soit, la Escclé la ferme.

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


Si vous savez que la boîte de dialogue contient des éléments pouvant être mis au point, vous n'aurez pas besoin de faire la mise au point directement. Si vous créez un menu, vous pouvez plutôt concentrer le premier élément de menu.

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


Rôles WAI-ARIA et autres supports d'accessibilité

J'espère 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 importante, je vais éviter toute discussion sur les rôles et les attributs WAI-ARIA , mais je recommande vivement aux implémenteurs de se référer à la spécification pour plus de détails. quels rôles ils devraient utiliser et tout autre attribut approprié.

zzzzBov
la source
30
C'est la réponse la plus complète, avec des explications et une accessibilité à l'esprit. Je pense que cela devrait être la réponse acceptée car la plupart des autres réponses ne traitent que les clics et ne sont que des extraits de code supprimés sans aucune explication.
Cyrille
4
Incroyable, bien expliqué. Je viens d'utiliser cette approche sur un composant React et j'ai parfaitement fonctionné grâce
David Lavieri
2
@zzzzBov merci pour la réponse profonde, j'essaye de le réaliser dans vanilla JS, et je suis un peu perdu avec tout le truc jquery. y a-t-il quelque chose de similaire dans vanilla js?
HendrikEng
2
@zzzzBov non je ne vous cherche pas pour écrire une version sans jQuery bien sûr, je vais essayer de le faire et je suppose que le mieux est de poser une nouvelle question ici si je suis vraiment coincé. Merci encore beaucoup.
HendrikEng
7
Bien qu'il s'agisse d'une excellente méthode pour détecter les clics sur des listes déroulantes personnalisées ou d'autres entrées, elle ne devrait jamais être la méthode préférée pour les modaux ou les fenêtres contextuelles pour deux raisons. Le modal se ferme lorsque l'utilisateur bascule vers un autre onglet ou une autre fenêtre, ou ouvre un menu contextuel, ce qui est vraiment f * ennuyeux. De plus, l'événement «clic» se déclenche lorsque la souris est levée, tandis que l'événement «focusout» se déclenche à l'instant où vous appuyez sur la souris. Normalement, un bouton n'exécute une action que si vous appuyez sur le bouton de la souris et que vous le relâchez. La manière appropriée et accessible de le faire pour les modaux consiste à ajouter un bouton de fermeture tabable.
Kevin
144

Les autres solutions ici ne fonctionnaient pas pour moi, j'ai donc dû utiliser:

if(!$(event.target).is('#foo'))
{
    // hide menu
}
Dennis
la source
J'ai publié un autre exemple pratique de la façon d'utiliser event.target pour éviter de déclencher d'autres gestionnaires html de widget hors interface Jquery lorsque vous les incorporez dans votre propre boîte contextuelle: le meilleur moyen d'obtenir la cible d'origine
Joey T
43
Cela a fonctionné pour moi, sauf que j'ai ajouté && !$(event.target).parents("#foo").is("#foo")à l'intérieur de l' IFinstruction afin qu'aucun élément enfant ne ferme le menu lorsque vous cliquez dessus.
honyovk
1
Impossible de trouver mieux que: // Nous sommes en dehors de $ (event.target) .parents ('# foo'). Length == 0
AlexG
3
L'amélioration concise pour gérer l'imbrication profonde est à utiliser .is('#foo, #foo *'), mais je ne recommande pas de lier les gestionnaires de clics pour résoudre ce problème .
zzzzBov
1
!$(event.target).closest("#foo").lengthserait mieux et évite d'avoir à ajouter @ honyovk.
tvanc
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 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 la one()fonction de jQuery

Joe Lencioni
la source
9
mais si vous cliquez sur le menu lui-même, puis à l'extérieur, cela ne fonctionnera pas :)
vsync
3
Il est utile de mettre event.stopProgagantion () avant de lier l'écouteur de clic au corps.
Jasper Kennis
4
Le problème avec cela est que "un" s'applique à la méthode jQuery consistant à ajouter plusieurs fois des événements à un tableau. Donc, si vous cliquez sur le menu pour l'ouvrir plus d'une fois, l'événement est à nouveau lié au corps et tente de masquer le menu plusieurs fois. Une sécurité intégrée doit être appliquée pour résoudre ce problème.
marksyzm
Après la liaison en utilisant .one- à l'intérieur du $('html')gestionnaire - écrivez a $('html').off('click').
Cody
4
@Cody Je ne pense pas que cela aide. Le onegestionnaire appellera automatiquement off(comme indiqué dans les documents jQuery).
Mariano Desanze
44

Après la 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>
Rameez Rami
la source
8
La troisième solution est de loin la manière la plus élégante de vérifier. Il n'implique pas non plus de surcharge de jQuery. Très agréable. Cela a beaucoup aidé. Merci.
dbarth
J'essaie de faire fonctionner la troisième solution avec plusieurs éléments document.getElementsByClassName, si quelqu'un a un indice, partagez-le.
lowtechsun
@lowtechsun Il faudrait parcourir pour vérifier chacun.
Donnie D'Amato
J'ai vraiment aimé la troisième solution, mais le déclic se déclenche avant que ma div ne commence à montrer ce qui la cache à nouveau, une idée pourquoi?
indiehjaerta
Le troisième autorise console.log, bien sûr, mais il ne vous permet pas de fermer réellement en définissant display sur none - car il le fera avant que le menu ne soit affiché. Avez-vous une solution à cela?
RolandiXor
40
$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

Fonctionne très bien pour moi.

user212621
la source
3
C'est celui que j'utiliserais. Ce n'est peut-être pas parfait, mais en tant que programmeur amateur, c'est assez simple pour comprendre clairement.
kevtrout
le flou est un mouvement en dehors de la #menucontainerquestion était sur un clic
borrel
4
Le flou @borrel n'est pas un déplacement à l'extérieur du conteneur. Le flou est l'opposé de la mise au point, vous pensez au mouseout. Cette solution a particulièrement bien fonctionné pour moi lorsque je créais du texte "cliquez pour modifier" où je permutais entre le texte brut et les champs de saisie lors des clics.
parker.sikand
J'ai dû ajouter tabindex="-1"au #menuscontainerpour le faire fonctionner. Il semble que si vous placez une balise d'entrée à l'intérieur du conteneur et cliquez dessus, le conteneur est masqué.
tyrion
l'événement mouseleave est plus approprié pour les menus et les conteneurs (ref: w3schools.com/jquery/… )
Evgenia Manolova
38

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

Ce qui suit se produit lorsqu'un gestionnaire clickoutside (WLOG) est lié à un élément:

  • l'élément est ajouté à un tableau qui contient tous les éléments avec clickoutside gestionnaires
  • un ( espace de noms ) cliquez gestionnaire de est lié au document (s'il n'y est pas déjà)
  • à chaque clic dans le document, l' événement clickoutside est déclenché pour les éléments de ce tableau qui ne sont pas égaux ou parent du clic cible -events
  • en outre, le event.target pour l' événement clickoutside est défini sur l'élément sur lequel l'utilisateur a cliqué (de sorte que vous savez même sur quoi l'utilisateur a cliqué, pas seulement qu'il a cliqué à l'extérieur)

Ainsi, aucun événement n'est arrêté de la propagation et des gestionnaires de clics supplémentaires peuvent être utilisés "au-dessus" de l'élément avec le gestionnaire externe.

Wolfram
la source
Joli plugin. Le lien sur les "événements extérieurs" est mort, tandis que le lien de l'article de blog est vivant à la place et fournit un plugin très utile pour les événements de type "clickoutside". Il est également sous licence MIT.
TechNyquist
Super plugin. Fonctionne parfaitement. L'utilisation est comme ça:$( '#element' ).on( 'clickoutside', function( e ) { .. } );
Gavin
33

Cela a fonctionné parfaitement pour moi !!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});
srinath
la source
Cette solution a bien fonctionné pour ce que je faisais, où j'avais encore besoin de l'événement click pour bouillonner, merci.
Mike
26

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

Trouver aucune réponse satisfaisante ci-dessus m'a incité à écrire cet article de blog l'autre jour. Pour les plus pédants, il existe un certain nombre de pièges à noter:

  1. Si vous attachez un gestionnaire d'événements click à l'élément body au moment du clic, assurez-vous d'attendre le 2ème clic avant de fermer le menu et de dissocier l'événement. Sinon, l'événement de clic qui a ouvert le menu bouillonnera jusqu'à l'auditeur qui doit fermer le menu.
  2. Si vous utilisez event.stopPropogation () sur un événement de clic, aucun autre élément de votre page ne peut avoir une fonction de clic n'importe où pour fermer.
  3. Attacher indéfiniment un gestionnaire d'événements de clic à l'élément de corps 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, c'est fermer le menu lorsque vous cliquez dessus, alors que ce que vous voulez vraiment, c'est 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. Un style aussi innocent que cela le briserait:body { margin-left:auto; margin-right: auto; width:960px;}
34m0
la source
2
"Si vous cliquez sur le menu, ou hors du menu, il devrait se fermer, n'est-ce pas?" pas toujours. L'annulation d'un clic en faisant glisser un élément déclenchera toujours un clic au niveau du document, mais l'intention ne serait pas de continuer à fermer le menu. Il existe également de nombreux autres types de boîtes de dialogue qui pourraient utiliser le comportement de "clic" qui permettrait de cliquer en interne.
zzzzBov
25

Comme l'a dit une autre affiche, 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 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);
        }

    })
});
Ben B
la source
25

Une solution simple à la situation est:

$(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 le divsi en dehors de ladiv déclenchement événement en événement click.

Vous pouvez consulter le blog suivant pour plus d'informations: http://www.codecanal.com/detect-click-outside-div-using-javascript/

Jitendra Damor
la source
22

Solution1

Au lieu d'utiliser event.stopPropagation () qui peut avoir des effets secondaires, définissez simplement une variable d'indicateur simple et ajoutez une ifcondition. J'ai testé cela et travaillé 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";
    }
});

Solution2

Avec juste une ifcondition simple :

$(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
    }
});
Iman Sedighi
la source
J'ai utilisé cette solution avec un drapeau booléen et c'est bien aussi avec un DOm articulé et aussi si à l'intérieur de #menucontainer il y a beaucoup d'autres éléments
Migio B
La solution 1 fonctionne mieux, car elle gère les cas où la cible de clic est supprimée du DOM au moment où l'événement se propage vers le document.
Alice
21

Vérifiez la cible de l'événement de clic sur la fenêtre (elle doit se propager à la fenêtre, tant qu'elle n'est capturée nulle part ailleurs), et assurez-vous qu'il ne s'agit d'aucun des éléments de menu. Si ce n'est pas le cas, alors vous êtes en dehors de votre menu.

Ou vérifiez la position du clic et voyez s'il est contenu dans la zone de menu.

Chris MacDonald
la source
18

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 #menuscontainers'affiche, liez un gestionnaire de clic au corps qui #menuscontainerne se cache que si la cible (du clic) n'en est pas un enfant.

Chu Yeow
la source
17

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'a aucun problème avec l' arrêt de la propagation des événements et prend mieux en charge 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.

Lyzanets Bohdan
la source
16

L'événement possède une propriété appelée event.path de l'élément qui est une "liste ordonnée statique de tous ses ancêtres dans un arbre" . Pour vérifier si un événement provient d'un élément DOM spécifique ou de l'un de ses enfants, vérifiez simplement le chemin d'accès à cet élément DOM spécifique. Il peut également être utilisé pour vérifier plusieurs éléments en exécutant logiquement ORla vérification des éléments dans la somefonction.

$("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>

Donc, pour votre cas, cela devrait être

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});
Dan Philip
la source
1
Ne fonctionne pas dans Edge.
Legends
event.pathn'est pas une chose.
Andrew
14

J'ai trouvé cette méthode dans un plugin de calendrier 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);
nazar kuliyev
la source
13

Voici la solution JavaScript vanille pour les futurs téléspectateurs.

Lorsque vous cliquez sur un élément du document, si l'ID de l'élément cliqué est basculé, ou que l'élément caché n'est pas masqué et que l'élément caché ne contient pas l'élément cliqué, basculez l'élément.

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

Si vous allez avoir plusieurs bascules sur la même page, vous pouvez utiliser quelque chose comme ceci:

  1. Ajoutez le nom de la classe hidden à l'élément pliable.
  2. Lors du clic sur le document, fermez tous les éléments masqués qui ne contiennent pas l'élément cliqué et qui ne sont pas masqués
  3. Si l'élément cliqué est une bascule, basculez l'élément spécifié.

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

utilisateur4639281
la source
13

Je suis surpris que personne n'ait reconnu l' focusoutévénement:

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>

Jovanni G
la source
Vous manquez ma réponse, je pense. stackoverflow.com/a/47755925/6478359
Muhammet Can TONBUL
Cela devrait être la réponse acceptée, directement au but. Merci!!!
Lucky Lam
8

Si vous créez des scripts pour IE et FF 3. * et que vous voulez simplement savoir si le clic s'est produit dans une certaine zone de boîte, vous pouvez également utiliser quelque chose comme:

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;}
Erik
la source
8

Au lieu d'utiliser l'interruption de flux, un événement flou / focus ou toute autre technique délicate, faites simplement correspondre le flux d'événements avec 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 l'écouteur d'événements hors clic, il suffit de:

$(document).off("click.menu-outside");
mems
la source
Pour moi, c'était la meilleure solution. Je l'ai utilisé dans un rappel après l'animation, donc j'avais besoin de détacher l'événement quelque part. +1
qwertzman
Il y a un contrôle redondant ici. si l'élément est en effet, #menuscontainervous passez encore par ses parents. vous devez d'abord vérifier cela, et si ce n'est pas cet élément, puis remonter l'arborescence DOM.
vsync
Droite ! Vous pouvez remplacer la condition par if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. C'est une minuscule optimisation, mais qui ne se produit que quelques fois dans la durée de vie de votre programme: pour chaque clic, si l' click.menu-outsideévénement est enregistré. Il est plus long (+32 caractères) et n'utilise pas de chaînage de méthode
mems
8

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();
});
webformasyon
la source
7

Si quelqu'un est curieux, voici la 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';
}

});

Walt
la source
7

Voici une solution simple en pur javascript. Il est à 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;
})
Duannx
la source
"À jour avec ES6" est une affirmation assez audacieuse, alors que la seule chose à jour avec ES6 fait à la () => {}place function() {}. Ce que vous avez là-bas est classé comme JavaScript simple avec une touche d'ES6.
MortenMoulder
@MortenMoulder: Ya. C'est juste pour attirer l'attention même s'il s'agit en fait d'ES6. Mais regardez la solution. Je pense que c'est bon.
Duannx
C'est vanS JS et fonctionne pour la cible d'événement supprimée du DOM (par exemple, lorsque la valeur de la fenêtre contextuelle interne est sélectionnée, ce qui ferme immédiatement la fenêtre contextuelle). +1 de moi!
Alice
6

Accrochez un écouteur d'événement de clic sur le document. À l'intérieur de l'écouteur d'événement, vous pouvez regarder l' objet événement , en particulier, event.target pour voir 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");
    }
});
Salman A
la source
6

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

Trouvez ci-dessous 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

Rinto George
la source
5
$(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.

Rowan
la source
5

Votez pour la réponse la plus populaire, mais ajoutez

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

donc, un clic sur une barre de défilement ne [cache pas ou quoi que ce soit] votre élément cible.

bbe
la source
5

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: la cible 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, de sorte que vous pouvez utiliser ce que vous attendez d'un rappel de 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 Click est placé sur le document. Cependant, si vous souhaitez limiter la portée de l'écouteur d'événements, vous pouvez passer un objet jQuery représentant un élément de niveau parent qui sera le parent supérieur auquel les clics seront écoutés. Cela empêche les écouteurs d'événements de niveau document inutiles. De toute évidence, cela ne fonctionnera que si l'élément parent fourni est un parent de votre élément initial.

Utilisez comme ça:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});
Matt Goodwin
la source