Domanda Come posso rilevare un clic all'esterno di un elemento?


Ho alcuni menu HTML, che mostro completamente quando un utente fa clic sulla testa di questi menu. Vorrei nascondere questi elementi quando l'utente fa clic all'esterno dell'area dei menu.

È qualcosa di simile a questo possibile con jQuery?

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

2023


origine


risposte:


NOTA: Uso stopEventPropagation() è qualcosa che dovrebbe essere evitato in quanto interrompe il normale flusso di eventi nel DOM. Vedere Questo articolo per maggiori informazioni. Considerare l'utilizzo questo metodo anziché.

Allegare un evento click al corpo del documento che chiude la finestra. Allegare un evento click separato alla finestra che interrompe la propagazione al corpo del documento.

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

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

1618



Puoi ascoltare per clic evento su document e quindi assicurati #menucontainer non è un antenato o il target dell'elemento cliccato usando .closest().

Se non lo è, allora l'elemento cliccato è al di fuori del #menucontainer e puoi tranquillamente nasconderlo.

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

Modifica - 2017-06-23

Puoi anche ripulire dopo il listener di eventi se prevedi di chiudere il menu e vuoi smettere di ascoltare gli eventi. Questa funzione pulirà solo il listener appena creato, conservando tutti gli altri listener di clic document. Con la sintassi ES2015:

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

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

  document.addEventListener('click', outsideClickListener)
}

Modifica - 2018-03-11

Per coloro che non vogliono usare jQuery. Ecco il codice sopra 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 

NOTA: Questo è basato sul commento di Alex da usare !element.contains(event.target) invece della parte jQuery.

Ma element.closest() è ora disponibile anche in tutti i principali browser (la versione W3C differisce un po 'da quella di jQuery). Polyfills può essere trovato qui: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest


1123



Come rilevare un clic all'esterno di un elemento?

La ragione per cui questa domanda è così popolare e ha così tante risposte è che è ingannevolmente complessa. Dopo quasi otto anni e dozzine di risposte, sono sinceramente sorpreso di vedere quanto poca attenzione sia stata data all'accessibilità.

Vorrei nascondere questi elementi quando l'utente fa clic all'esterno dell'area dei menu.

Questa è una nobile causa ed è la effettivo problema. Il titolo della domanda, che è ciò che la maggior parte delle risposte sembra tentare di affrontare, contiene una sfortunata falsa pista.

Suggerimento: è la parola "clic"!

In realtà non vuoi associare gestori di clic.

Se stai vincolando i gestori di clic per chiudere la finestra di dialogo, hai già fallito. La ragione per cui hai fallito è che non tutti si innescano click eventi. Gli utenti che non usano il mouse saranno in grado di sfuggire alla finestra di dialogo (e il menu a comparsa è probabilmente un tipo di finestra di dialogo) premendo linguettae quindi non saranno in grado di leggere il contenuto dietro il dialogo senza successivamente attivare a click evento.

Quindi riformuliamo la domanda.

Come si chiude una finestra di dialogo quando un utente ha finito con esso?

Questo è l'obiettivo Purtroppo, ora abbiamo bisogno di legare il userisfinishedwiththedialog evento, e quell'associazione non è così semplice.

Quindi, come possiamo rilevare che un utente ha finito di usare una finestra di dialogo?

focusout evento

Un buon inizio è determinare se la messa a fuoco ha lasciato la finestra di dialogo.

Suggerimento: stai attento con il blur evento, blur non si propaga se l'evento era legato alla fase di bubbling!

jQuery di focusout andrà bene. Se non puoi usare jQuery, puoi usare blur durante la fase di cattura:

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

Inoltre, per molte finestre di dialogo è necessario consentire al contenitore di concentrarsi. Inserisci tabindex="-1" per consentire al dialogo di ricevere la messa a fuoco in modo dinamico senza interrompere in altro modo il flusso di tabulazione.

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


Se giochi con quella demo per più di un minuto dovresti iniziare subito a vedere i problemi.

Il primo è che il link nella finestra di dialogo non è selezionabile. Tentando di fare clic su di esso o sulla scheda, si aprirà la finestra di dialogo prima che avvenga l'interazione. Questo perché focalizzare l'elemento interno innesca a focusout evento prima di attivare a focusin evento di nuovo.

La correzione è di accodare la modifica dello stato sul ciclo degli eventi. Questo può essere fatto usando setImmediate(...), o setTimeout(..., 0) per i browser che non supportano setImmediate. Una volta accodato, può essere annullato da un successivo 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>

Il secondo problema è che la finestra di dialogo non si chiude quando il link viene premuto di nuovo. Questo perché la finestra di dialogo perde lo stato attivo, attivando il comportamento di chiusura, dopo di che il clic del collegamento attiva la finestra di dialogo per riaprire.

Simile al problema precedente, lo stato di messa a fuoco deve essere gestito. Dato che la modifica dello stato è già stata accodata, è solo questione di gestire gli eventi di attivazione sui trigger di dialogo:

Questo dovrebbe sembrare familiare
$('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 chiave

Se pensavi di aver finito gestendo gli stati di attivazione, puoi fare di più per semplificare l'esperienza dell'utente.

Questa è spesso una funzionalità "bella da avere", ma è comune che quando si ha una modal o una pop-up di qualsiasi tipo che la Esc il tasto lo chiuderà.

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>


Se sai di avere elementi attivabili nella finestra di dialogo, non dovrai mettere a fuoco direttamente la finestra di dialogo. Se stai creando un menu, puoi invece concentrare la prima voce del 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.


Ruoli di WAI-ARIA e altro supporto di accessibilità

Questa risposta si spera copra le basi del supporto accessibile per tastiera e mouse per questa funzione, ma poiché è già abbastanza considerevole, eviterò qualsiasi discussione su Ruoli e attributi WAI-ARIA, tuttavia, io altamente consiglia agli implementatori di fare riferimento alle specifiche per i dettagli su quali ruoli devono essere utilizzati e su eventuali altri attributi appropriati.


184



Le altre soluzioni qui non hanno funzionato per me quindi ho dovuto usare:

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

127



Ho un'applicazione che funziona in modo simile all'esempio di Eran, tranne che allego l'evento click al corpo quando apro il menu ... Un po 'come questo:

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

  event.stopPropagation();
});

Maggiori informazioni su jQuery di one() funzione


118



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

Funziona per me bene.


32



Ora c'è un plugin per questo: eventi esterni (post sul blog)

Il seguente accade quando a clickoutside handler (WLOG) è associato a un elemento:

  • l'elemento viene aggiunto a un array che contiene tutti gli elementi clickoutside gestori
  • a (namespace) clic gestore è legato al documento (se non già presente)
  • su qualsiasi clic nel documento, il clickoutside l'evento viene attivato per quegli elementi in quell'array che non sono uguali o un genitore di clic-eventi obiettivo
  • inoltre, event.target per il clickoutside l'evento è impostato sull'elemento su cui l'utente ha fatto clic (in modo da sapere anche cosa l'utente ha fatto clic, non solo che ha fatto clic all'esterno)

Quindi nessun evento viene fermato dalla propagazione e aggiuntivo clic i gestori possono essere utilizzati "sopra" l'elemento con l'operatore esterno.


31



Questo ha funzionato perfettamente per me !!

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

26



Dopo la ricerca ho trovato tre soluzioni funzionanti (ho dimenticato i collegamenti alle pagine per riferimento)

Prima soluzione

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

Seconda soluzione

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

Terza soluzione

<script>
    var specifiedElement = document.getElementById('testDiv');
    document.addEventListener('click', function(event) {
        var isClickInside = specifiedElement.contains(event.target);
        if (isClickInside) {
          console.log('You clicked inside')
        }
        else {
          console.log('You clicked outside')
        }
    });
</script>

24



Non penso che quello di cui hai veramente bisogno sia chiudere il menu quando l'utente fa clic all'esterno; ciò di cui hai bisogno è che il menu si chiuda quando l'utente fa clic in qualsiasi punto della pagina. Se si fa clic sul menu o fuori dal menu, dovrebbe chiudersi correttamente?

Non trovando risposte soddisfacenti in alto mi ha spinto a scrivere questo post del blog l'altro giorno. Per i più pedanti, ci sono un certo numero di trucchi per prendere nota di:

  1. Se alleghi un gestore di eventi click all'elemento body al momento del clic, assicurati di attendere il secondo clic prima di chiudere il menu e di slegare l'evento. Altrimenti l'evento click che ha aperto il menu diventerà visibile all'ascoltatore che deve chiudere il menu.
  2. Se si utilizza event.stopPropogation () su un evento click, nessun altro elemento della pagina può avere una funzionalità click-anywhere-to-close.
  3. Allegare un gestore di eventi click all'elemento body indefinitamente non è una soluzione performante
  4. Confrontando il target dell'evento, e i suoi genitori con il gestore del gestore, si presume che quello che si desidera sia chiudere il menu quando si fa clic su di esso, quando ciò che si vuole veramente è chiuderlo quando si fa clic in qualsiasi punto della pagina.
  5. Ascoltare eventi sull'elemento body renderà il tuo codice più fragile. Styling innocente come questo lo spezzerebbe: body { margin-left:auto; margin-right: auto; width:960px;}

22



Come un altro poster ha detto che ci sono molti trucchi, specialmente se l'elemento che stai visualizzando (in questo caso un menu) ha elementi interattivi. Ho trovato il seguente metodo per essere abbastanza robusto:

$('#menuscontainer').click(function(event) {
    //your code that shows the menus fully

    //now set up an event listener so that clicking anywhere outside will close the menu
    $('html').click(function(event) {
        //check up the tree of the click target to check whether user has clicked outside of menu
        if ($(event.target).parents('#menuscontainer').length==0) {
            // your code to hide menu

            //this event listener has done its job so we can unbind it.
            $(this).unbind(event);
        }

    })
});

21