Vraag Hoe kan ik een klik buiten een element detecteren?


Ik heb een aantal HTML-menu's die ik volledig laat zien wanneer een gebruiker op de kop van deze menu's klikt. Ik wil deze elementen verbergen als de gebruiker buiten het menu klikt.

Is zoiets als dit mogelijk met jQuery?

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

2023


oorsprong


antwoorden:


OPMERKING: gebruik van stopEventPropagation() is iets dat moet worden vermeden omdat het normale gebeurtenissen in de DOM doorbreekt. Zien Dit artikel voor meer informatie. Overweeg het gebruik van deze methode in plaats daarvan.

Bevestig een klikgebeurtenis aan de documenttekst die het venster sluit. Voeg een afzonderlijke klikgebeurtenis toe aan het venster dat de verspreiding naar de hoofdtekst van het document stopt.

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

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

1618



Je kunt luisteren naar een Klik evenement aan document en zorg er dan voor #menucontainer is geen voorouder of het doelwit van het aangeklikte element door te gebruiken .closest().

Als dit niet het geval is, bevindt het element waarop wordt geklikt zich buiten de #menucontainer en je kunt het veilig verbergen.

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

Bewerken - 2017-06-23

Je kunt ook opruimen na de gebeurtenislistener als je van plan bent het menu te sluiten en niet meer wilt luisteren naar evenementen. Met deze functie wordt alleen de nieuw gemaakte listener opgeruimd en blijven andere klikluisteraars behouden document. Met ES2015-syntaxis:

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

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

  document.addEventListener('click', outsideClickListener)
}

Bewerken - 2018-03-11

Voor degenen die jQuery niet willen gebruiken. Hier is de bovenstaande code in plain vanillaJS (ECMAScript6).

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

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

    document.addEventListener('click', outsideClickListener)
}

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

NOTITIE: Dit is gebaseerd op Alex commentaar om gewoon te gebruiken !element.contains(event.target) in plaats van het jQuery-gedeelte.

Maar element.closest() is nu ook beschikbaar in alle belangrijke browsers (de W3C-versie verschilt een beetje van de jQuery-versie). Polyfills zijn hier te vinden: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest


1123



Hoe een klik buiten een element te detecteren?

De reden dat deze vraag zo populair is en zoveel antwoorden heeft, is dat het bedrieglijk ingewikkeld is. Na bijna acht jaar en tientallen antwoorden ben ik oprecht verrast om te zien hoe weinig aandacht is besteed aan toegankelijkheid.

Ik wil deze elementen verbergen als de gebruiker buiten het menu klikt.

Dit is een nobele zaak en dat is het werkelijk kwestie. De titel van de vraag - wat de meeste antwoorden lijken te proberen aan te pakken - bevat een ongelukkige rode haring.

Hint: het is het woord "Klik"!

U wilt click-handlers niet binden.

Als u bindende klikhandlers bent om het dialoogvenster te sluiten, is uw fiasco al mislukt. De reden dat je hebt gefaald, is dat niet iedereen triggert click evenementen. Gebruikers die geen muis gebruiken, kunnen aan uw dialoogvenster ontsnappen (en uw pop-upmenu is aantoonbaar een soort dialoogvenster) door op tab, en zij zullen dan de inhoud achter de dialoog niet kunnen lezen zonder vervolgens een a te triggeren click evenement.

Dus laten we de vraag anders formuleren.

Hoe sluit men een dialoogvenster wanneer een gebruiker ermee klaar is?

Dit is het doel. Helaas moeten we nu de. Binden userisfinishedwiththedialog evenement, en die binding is niet zo eenvoudig.

Dus hoe kunnen we detecteren dat een gebruiker klaar is met het gebruik van een dialoogvenster?

focusout evenement

Een goed begin is om te bepalen of focus de dialoog heeft verlaten.

Hint: wees voorzichtig met de blur evenement, blur verspreidt zich niet als de gebeurtenis gebonden was aan de bubbling-fase!

jQuery focusout zal het goed doen. Als u jQuery niet kunt gebruiken, kunt u gebruiken blur tijdens de vangstfase:

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

Ook moet je voor veel dialoogvensters toestaan ​​dat de container de focus krijgt. Toevoegen tabindex="-1" om het dialoogvenster toe te staan ​​om de focus dynamisch te ontvangen zonder de tabbladenstroom anderszins te onderbreken.

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


Als je langer dan een minuut met die demo speelt, zou je snel problemen moeten zien.

De eerste is dat de link in het dialoogvenster niet klikbaar is. Als u erop klikt of ernaar tabelt, wordt het dialoogvenster gesloten voordat de interactie plaatsvindt. Dit komt omdat het richten van het innerlijke element een a activeert focusout gebeurtenis voor het activeren van a focusin evenement opnieuw.

De oplossing is om de statuswijziging in de gebeurtenislus in de wachtrij te plaatsen. Dit kan gedaan worden door setImmediate(...)of setTimeout(..., 0) voor browsers die geen ondersteuning bieden setImmediate. Eenmaal in de wachtrij kan deze door een volgende worden geannuleerd 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>

Het tweede probleem is dat het dialoogvenster niet wordt gesloten wanneer de link opnieuw wordt ingedrukt. Dit komt omdat het dialoogvenster de focus verliest, waardoor het sluitgedrag wordt geactiveerd, waarna de linkklik het dialoogvenster activeert om opnieuw te openen.

Net als bij het vorige nummer moet de focusstatus worden beheerd. Aangezien de statuswijziging al in de wachtrij is geplaatst, is het gewoon een kwestie van het afhandelen van focusgebeurtenissen op de dialoogtriggers:

Dit moet bekend voorkomen
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

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

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

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


Esc sleutel

Als je dacht dat je klaar was door de focusstatussen te gebruiken, kun je nog meer doen om de gebruikerservaring te vereenvoudigen.

Dit is vaak een "leuk om te hebben" -functie, maar het is gebruikelijk dat wanneer u een modale of popup van welke aard dan ook de Esc sleutel sluit het uit.

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>


Als u weet dat u focuseerbare elementen in het dialoogvenster hebt, hoeft u het dialoogvenster niet direct te richten. Als u een menu aan het maken bent, kunt u in plaats daarvan het eerste menu-item gebruiken.

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

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

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

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

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

.submenu__item {
  width: 150px;
}

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

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


WAI-ARIA-rollen en andere ondersteuning voor toegankelijkheid

Dit antwoord behandelt hopelijk de basis van toegankelijke toetsenbord- en muisondersteuning voor deze functie, maar aangezien het al behoorlijk groot is, ga ik elke discussie vermijden over WAI-ARIA rollen en attributen, Hoewel, ik zeer beveel aan dat uitvoerders naar de specificatie verwijzen voor details over welke rollen ze moeten gebruiken en andere geschikte attributen.


184



De andere oplossingen hier werkten niet voor mij dus ik moest gebruiken:

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

127



Ik heb een applicatie die op dezelfde manier werkt als het voorbeeld van Eran, behalve dat ik de klikgebeurtenis aan het lichaam koppel wanneer ik het menu open ... Nogal zo:

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

  event.stopPropagation();
});

Meer informatie over jQuery one() functie


118



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

Werkt prima voor mij.


32



Nu is daar een plugin voor: externe evenementen (blogpost)

Het volgende gebeurt wanneer a clickoutside handler (WLOG) is gebonden aan een element:

  • het element wordt toegevoegd aan een array die alle elementen bevat clickoutside handlers
  • een (naamruimten) Klik handler is gebonden aan het document (als het er nog niet is)
  • op elke Klik in het document, de clickoutside gebeurtenis wordt geactiveerd voor die elementen in die array die niet gelijk zijn aan of een bovenliggend element van de Klik-doelstellingen
  • bovendien is de event.target voor de clickoutside gebeurtenis is ingesteld op het element waarop de gebruiker heeft geklikt (zodat u weet waar de gebruiker op heeft geklikt, niet alleen dat hij buiten heeft geklikt)

Dus geen evenementen worden gestopt voor propagatie en extra Klik handlers mogen "boven" het element met de buitenafhandelaar worden gebruikt.


31



Dit werkte perfect voor mij !!

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

26