javascript - élément - syntaxe fonction onclick




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

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.

C'est une noble cause et c'est 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.

Indice: c'est le mot "clic" !

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

Si vous liez des gestionnaires de clic 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 de 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 la touche de tabulation . Ils ne pourront alors pas lire le contenu situé derrière la boîte de dialogue sans déclencher un click un événement.

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?

événement focusout

Un bon début consiste à déterminer si le focus est sorti du dialogue.

Astuce: soyez prudent avec l'événement de blur , le blur ne se propage pas si l'événement était lié à la phase de formation de bulles!

La focusout de jQuery fera l'affaire. Si vous ne pouvez pas utiliser jQuery, vous pouvez utiliser le 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 de mise au point avant de le déclencher à nouveau.

Le correctif consiste à mettre en file d'attente le changement d'état sur la boucle d'événement. Pour ce faire, utilisez setImmediate(...) ou setTimeout(..., 0) pour les navigateurs ne setImmediate pas en charge setImmediate . Une fois en file d'attente, il peut être annulé par un focusin :

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

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

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

Le deuxième problème est que la boîte de dialogue ne se 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 provoque 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>

Touche echap

Si vous pensiez que vous aviez terminé en gérant les états prioritaires, vous pouvez faire davantage 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 un popup de quelque sorte que ce soit, la touche Échap la ferme.

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.

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

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 les rôles et attributs de WAI-ARIA . Je recommande toutefois vivement aux implémenteurs de se reporter aux spécifications. sur quels rôles ils devraient utiliser et tout autre attribut approprié.

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

Après des recherches, j'ai trouvé trois solutions de travail (j'ai oublié les liens de 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>

Cela a fonctionné pour moi parfaitement !!

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

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 à css-tricks.com/dangers-stopping-event-propagation 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.


Il existe maintenant 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 des gestionnaires clickoutside
  • un gestionnaire de clics ( namespaced ) est lié au document (s'il n'y en a pas déjà un)
  • sur n'importe quel clic dans le document, l'événement clickoutside est déclenché pour les éléments de ce tableau qui ne sont pas égaux ou parents de la cible click -events
  • De plus, event.target pour l'événement clickoutside est défini sur l'élément sur lequel l'utilisateur a cliqué (vous savez même sur quoi l'utilisateur a cliqué, pas seulement sur l'extérieur).

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


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

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 la fonction one() de jQuery


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

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

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 cachera la div si un événement en dehors de l'événement clic est déclenché.

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


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 capturée nulle part ailleurs), et assurez-vous qu'elle ne fait pas partie 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.


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

Lorsque vous cliquez sur un élément du document, si l'identifiant de l'élément sur lequel vous cliquez est modifié ou si l'élément masqué n'est pas masqué et si l'élément masqué ne contient pas l'élément sur lequel vous avez 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);
})();

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

  1. Ajoutez le nom hiddende la classe à l'élément compressible.
  2. En cliquant sur le document, fermez tous les éléments masqués qui ne contiennent pas l’élément sélectionné et ne sont pas masqués.
  3. Si l'élément sur lequel vous avez 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>


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

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

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 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 parent principal auquel les clics seront écoutés. Cela évite les écouteurs d’événement inutiles au niveau du document. É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...
});

Si vous créez un script pour IE et FF 3. * et que vous voulez simplement savoir si le clic a eu lieu dans une zone donnée, 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;}

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 votre élément cible.


Accrochez un écouteur d'événement de clic sur le document. Dans l'écouteur d'événements, vous pouvez examiner l' objet événement , en particulier le event.target pour savoir 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");
    }
});

Nous avons implémenté une solution, basée en partie sur un commentaire 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.


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

Ça marche très bien pour moi.







jquery