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


2486

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 al di fuori dell'area dei menu.

È possibile qualcosa di simile con jQuery?

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

47
Ecco un esempio di questa strategia: jsfiddle.net/tedp/aL7Xe/1
Ted

18
Come accennato da Tom, ti consigliamo di leggere css-tricks.com/dangers-stopping-event-propagation prima di utilizzare questo approccio. Lo strumento jsfiddle è piuttosto interessante.
Jon Coombs,

3
ottenere un riferimento all'elemento e quindi event.target, e infine! = o == entrambi eseguono il codice di conseguenza ..
Rohit Kumar


2
Soluzione JS alla vaniglia con event.targete senza event.stopPropagation .
lowtechsun,

Risposte:


1812

NOTA: l'utilizzo stopEventPropagation()è qualcosa che dovrebbe essere evitato in quanto interrompe il normale flusso di eventi nel DOM. Vedi questo articolo per maggiori informazioni. Prendi invece in considerazione l'utilizzo di questo metodo

Allega un evento clic al corpo del documento che chiude la finestra. Collegare un evento clic separato al contenitore che interrompe la propagazione al corpo del documento.

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

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

708
Ciò interrompe il comportamento standard di molte cose, inclusi pulsanti e collegamenti, contenuti in #menucontainer. Sono sorpreso che questa risposta sia così popolare.
Art

75
Questo non interrompe il comportamento di qualcosa all'interno di #menucontainer, poiché è in fondo alla catena di propagazione per qualsiasi cosa al suo interno.
Eran Galperin,

94
è molto bello ma non dovresti usare il $('html').click()corpo. Il corpo ha sempre l'altezza del suo contenuto. Non c'è molto contenuto o lo schermo è molto alto, funziona solo sulla parte riempita dal corpo.
martedì

103
Sono anche sorpreso che questa soluzione abbia ottenuto così tanti voti. Questo fallirà per qualsiasi elemento esterno che ha stopPropagation jsfiddle.net/Flandre/vaNFw/3
Andre

140
Philip Walton spiega molto bene perché questa risposta non è la soluzione migliore: css-tricks.com/dangers-stopping-event-propagation
Tom

1387

Puoi ascoltare un evento clickdocument e poi assicurarti che #menucontainernon sia un antenato o la destinazione dell'elemento cliccato usando .closest().

In caso contrario, l'elemento cliccato si trova all'esterno di #menucontainere puoi tranquillamente nasconderlo.

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

Modifica - 23/06/2017

Puoi anche ripulire dopo il listener di eventi se prevedi di chiudere il menu e desideri interrompere l'ascolto degli eventi. Questa funzione pulirà solo il listener appena creato, preservando tutti gli altri listener di clic document. Con la sintassi 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)
}

Modifica - 11-03-2018

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

NOTA: si basa sul commento di Alex da usare al !element.contains(event.target)posto della parte jQuery.

Ma element.closest()ora è disponibile anche in tutti i principali browser (la versione W3C differisce leggermente da quella di jQuery). Polyfills può essere trovato qui: Element.closest ()

Modifica - 2020-05-21

Nel caso in cui desideri che l'utente sia in grado di fare clic e trascinare all'interno dell'elemento, quindi rilascia il mouse all'esterno dell'elemento, senza chiudere l'elemento:

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

E in 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()
        }
    }

28
Ho provato molte delle altre risposte, ma solo questa ha funzionato. Grazie. Il codice che ho finito per usare era questo: $ (documento) .click (funzione (evento) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('fast');}});
Pistos

39
In realtà ho finito con questa soluzione perché supporta meglio più menu nella stessa pagina in cui facendo clic su un secondo menu mentre un primo è aperto lascerà il primo aperto nella soluzione stopPropagation.
umassthrower

13
Risposta eccellente. Questa è la strada da percorrere quando si hanno più oggetti che si desidera chiudere.
Giovanni

5
Questa dovrebbe essere la risposta accettata a causa del difetto che l'altra soluzione ha con event.stopPropagation ().
pomodoro,

20
Senza jQuery - !element.contains(event.target)usando Node.contains ()
Alex Ross il

304

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 al di fuori dell'area dei menu.

Questa è una nobile causa ed è il vero problema. Il titolo della domanda - che è ciò che la maggior parte delle risposte sembrano tentare di rispondere - contiene una sfortunata aringa rossa.

Suggerimento: è la parola "clic" !

In realtà non si desidera associare i gestori di clic.

Se stai vincolando i gestori dei clic per chiudere la finestra di dialogo, hai già fallito. Il motivo per cui hai fallito è che non tutti attivano clickeventi. 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 Tab, e quindi non saranno in grado di leggere il contenuto dietro la finestra di dialogo senza attivare successivamente un clickevento.

Quindi riformuliamo la domanda.

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

Questo è l'obiettivo. Sfortunatamente, ora dobbiamo legare l' userisfinishedwiththedialogevento 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 lo stato attivo ha lasciato la finestra di dialogo.

Suggerimento: stai attento con l' blurevento, blurnon si propaga se l'evento è stato legato alla fase gorgogliante!

jQuery focusoutandrà benissimo. Se non puoi usare jQuery, puoi usarlo blurdurante la fase di acquisizione:

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

Inoltre, per molte finestre di dialogo è necessario consentire al contenitore di ottenere lo stato attivo. Aggiungi tabindex="-1"per consentire alla finestra di dialogo di ricevere lo stato attivo in modo dinamico senza interrompere altrimenti 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 a vedere rapidamente i problemi.

Il primo è che il collegamento nella finestra di dialogo non è selezionabile. Il tentativo di fare clic su di esso o sulla scheda porterà alla chiusura della finestra di dialogo prima che avvenga l'interazione. Questo perché focalizzare l'elemento interno innesca un focusoutevento prima di innescare di focusinnuovo un evento.

La correzione consiste nell'accodare la modifica dello stato nel loop degli eventi. Questo può essere fatto usando setImmediate(...)o setTimeout(..., 0)per i browser che non supportano setImmediate. Una volta in coda 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'));
  }
});

Il secondo problema è che la finestra di dialogo non si chiuderà quando si preme nuovamente il collegamento. Questo perché la finestra di dialogo perde lo stato attivo, innescando il comportamento di chiusura, dopodiché il clic sul collegamento avvia la finestra di dialogo per riaprirla.

Simile al problema precedente, lo stato attivo deve essere gestito. Dato che il cambio di stato è già stato messo in coda, si tratta solo di gestire gli eventi di attivazione nei 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'));  
  }
});


Esc chiave

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

Questa è spesso una funzione "piacevole da avere", ma è comune che quando si dispone di un modale o di un popup di qualsiasi tipo, la Escchiave lo chiude.

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


Se sai di avere elementi focalizzabili nella finestra di dialogo, non dovrai focalizzare direttamente la finestra di dialogo. Se stai creando un menu, puoi invece focalizzare la prima voce di menu.

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


Ruoli WAI-ARIA e altri supporti per l'accessibilità

Si spera che questa risposta copra le basi del supporto accessibile di tastiera e mouse per questa funzione, ma poiché è già abbastanza considerevole eviterò qualsiasi discussione sui ruoli e gli attributi di WAI-ARIA , tuttavia consiglio vivamente che gli implementatori facciano riferimento alle specifiche per i dettagli su quali ruoli dovrebbero usare e qualsiasi altro attributo appropriato.


29
Questa è la risposta più completa, con in mente spiegazioni e accessibilità. Penso che questa dovrebbe essere la risposta accettata poiché la maggior parte delle altre risposte gestisce solo il clic e viene semplicemente eliminato lo snippet di codice senza alcuna spiegazione.
Cyrille,

4
Incredibile, ben spiegato. Ho appena usato questo approccio su un componente React e ho lavorato perfettamente grazie
David Lavieri il

2
@zzzzBov grazie per la risposta profonda, sto provando a realizzarlo in JS vaniglia e mi sono perso un po 'con tutte le cose jquery. c'è qualcosa di simile in vanilla js?
Hendrik,

2
@zzzzBov no, non ti cerco di scrivere una versione priva di jQuery, cercherò di farlo e immagino che la cosa migliore da fare sia fare una nuova domanda qui se mi sono davvero bloccato. Grazie ancora.
Hendrik,

7
Sebbene questo sia un ottimo metodo per rilevare il clic da menu a discesa personalizzati o altri input, non dovrebbe mai essere il metodo preferito per modali o popup per due motivi. Il modale si chiuderà quando l'utente passerà a un'altra scheda o finestra o aprirà un menu di scelta rapida, il che è davvero fastidioso. Inoltre, l'evento "clic" si attiva al passaggio del mouse verso l'alto, mentre l'evento "focusout" attiva il momento in cui si preme il mouse verso il basso. Normalmente, un pulsante esegue un'azione solo se si preme il pulsante del mouse verso il basso e quindi si rilascia. Il modo corretto e accessibile per farlo per i modali è aggiungere un pulsante di chiusura tabulare.
Kevin,

144

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

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

Ho pubblicato un altro esempio pratico di come utilizzare event.target per evitare di attivare altri widget dell'interfaccia utente di Jquery fuori dai gestori di html fuori clic quando si incorporano nella propria finestra pop-over: Il modo migliore per ottenere l'obiettivo originale
Joey T

43
Questo ha funzionato per me, tranne che ho aggiunto && !$(event.target).parents("#foo").is("#foo")all'interno IFdell'istruzione in modo che eventuali elementi figlio non chiudano il menu quando si fa clic.
honyovk,

1
Non riesco a trovare di meglio di: // Siamo al di fuori di $ (event.target) .parents ('# foo'). Length == 0
AlexG

3
Il conciso miglioramento per gestire l'annidamento profondo è l'utilizzo .is('#foo, #foo *'), ma non consiglio di gestori di clic vincolanti per risolvere questo problema .
zzzzBov,

1
!$(event.target).closest("#foo").lengthsarebbe meglio e ovvia alla necessità di aggiungere @ honyovk.
tvanc,

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 ... Kinda in questo modo:

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

  event.stopPropagation();
});

Ulteriori informazioni sulla funzione di jQueryone()


9
ma poi se fai clic sul menu stesso, quindi all'esterno, non funzionerà :)
vsync

3
Aiuta a inserire event.stopProgagantion () prima di associare il listener di clic al corpo.
Jasper Kennis,

4
Il problema con questo è che "uno" si applica al metodo jQuery di aggiungere eventi a un array più volte. Quindi, se fai clic sul menu per aprirlo più di una volta, l'evento viene nuovamente associato al corpo e tenta di nascondere il menu più volte. È necessario applicare un fail-safe per risolvere questo problema.
Markyzm,

Dopo aver eseguito il binding utilizzando .one- all'interno del $('html')gestore - scrivi a $('html').off('click').
Cody,

4
@Cody Non penso che aiuti. Il onegestore chiamerà automaticamente off(come mostrato nei documenti jQuery).
Mariano Desanze,

44

Dopo la ricerca ho trovato tre soluzioni funzionanti (ho dimenticato i collegamenti alla pagina 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>

8
La terza soluzione è di gran lunga il modo più elegante di controllare. Inoltre non comporta alcun sovraccarico di jQuery. Molto bella. Mi ha aiutato molto. Grazie.
dbarth,

Sto cercando di ottenere la terza soluzione con più elementi document.getElementsByClassName, se qualcuno ha un indizio per favore condividi.
Lowtechsun

@lowtechsun Dovresti fare un giro per controllare ciascuno.
Donnie D'Amato,

Mi è davvero piaciuta la terza soluzione, tuttavia il clic si innesca prima che il mio div abbia iniziato a mostrare che lo nasconde di nuovo, hai idea del perché?
indiehjaerta,

Il terzo consente console.log, certo, ma non ti consente di chiudere effettivamente impostando display su none - perché lo farà prima che venga mostrato il menu. Hai una soluzione per questo?
RolandiXor

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

Funziona bene per me.


3
Questo è quello che userei. Potrebbe non essere perfetto, ma come programmatore di hobby, è abbastanza semplice da capire chiaramente.
kevtrout,

sfocatura è una mossa al di fuori della #menucontainerdomanda era su un clic
borrel

4
La sfocatura di @borrel non è una mossa fuori dal contenitore. La sfocatura è l'opposto del focus, stai pensando al mouseout. Questa soluzione ha funzionato particolarmente bene per me durante la creazione del testo "fai clic per modificare" in cui ho scambiato avanti e indietro tra testo semplice e campi di input sui clic.
parker.sikand

Ho dovuto aggiungere tabindex="-1"a #menuscontainerper farlo funzionare. Sembra che se si inserisce un tag di input all'interno del contenitore e si fa clic su di esso, il contenitore viene nascosto.
Tyrion,

l'evento mouseleave è più adatto a menu e contenitori (rif: w3schools.com/jquery/… )
Evgenia Manolova,

38

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

Ciò che accade quando un gestore clickoutside (WLOG) è associato a un elemento:

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

Pertanto, nessun evento viene interrotto dalla propagazione e è possibile utilizzare gestori di clic aggiuntivi "sopra" l'elemento con il gestore esterno.


Bel plugin. Il link su "eventi esterni" è morto, mentre il link post sul blog è invece attivo e fornisce un plug-in molto utile per eventi tipo "clickoutside". Ha anche la licenza MIT.
TechNyquist,

Ottimo plugin. Ha funzionato perfettamente. L'utilizzo è così:$( '#element' ).on( 'clickoutside', function( e ) { .. } );
Gavin,

33

Questo ha funzionato perfettamente per me !!

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

Questa soluzione ha funzionato bene per quello che stavo facendo, dove avevo ancora bisogno dell'evento click per saltare, grazie.
Mike,

26

Non penso che ciò 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 fai clic sul menu o al di fuori del menu, dovrebbe chiudersi giusto?

Non trovando risposte soddisfacenti sopra mi ha spinto a scrivere questo post sul blog l'altro giorno. Per i più pedanti, ci sono un certo numero di gotchas da prendere in considerazione:

  1. Se si collega un gestore di eventi click all'elemento body al momento del clic, assicurarsi di attendere il secondo clic prima di chiudere il menu e separare l'evento. Altrimenti, l'evento click che ha aperto il menu si sposterà sull'ascoltatore che deve chiudere il menu.
  2. Se si utilizza event.stopPropogation () su un evento clic, nessun altro elemento nella pagina può avere una funzione di clic ovunque per chiudere.
  3. Collegare un gestore di eventi click all'elemento body indefinitamente non è una soluzione performante
  4. Confrontando l'obiettivo dell'evento e i suoi genitori con il creatore del gestore, si presume che ciò che si desidera sia chiudere il menu quando si fa clic su di esso, quando ciò che si desidera realmente è chiuderlo quando si fa clic in un punto qualsiasi della pagina.
  5. L'ascolto di eventi sull'elemento body renderà il tuo codice più fragile. Uno stile innocente come questo lo spezzerebbe:body { margin-left:auto; margin-right: auto; width:960px;}

2
"Se fai clic sul menu o al di fuori del menu dovrebbe chiudersi giusto?" non sempre. L'annullamento di un clic trascinando un elemento attiverà comunque un clic a livello di documento, ma l'intento non sarebbe quello di continuare a chiudere il menu. Esistono anche molti altri tipi di finestre di dialogo che potrebbero utilizzare il comportamento "clic-out" che consentirebbe di fare clic internamente.
zzzzBov,

25

Come ha detto un altro poster, ci sono molti gotcha, specialmente se l'elemento che stai visualizzando (in questo caso un menu) ha elementi interattivi. Ho trovato il seguente metodo 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);
        }

    })
});

25

Una soluzione semplice per la situazione è:

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

Lo script sopra nasconderà divse divviene attivato l' evento esterno al clic.

Puoi vedere il seguente blog per maggiori informazioni: http://www.codecanal.com/detect-click-outside-div-using-javascript/


22

Soluzione 1

Invece di utilizzare event.stopPropagation () che può avere alcuni effetti collaterali, basta definire una semplice variabile flag e aggiungere una ifcondizione. Ho provato questo e ha funzionato correttamente senza effetti collaterali di 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

Con una semplice ifcondizione:

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

Ho usato questa soluzione con una bandiera booleana ed è buona anche con un DOm articolato e anche se all'interno di #menucontainer ci sono molti altri elementi
Migio B,

La soluzione 1 funziona meglio, perché gestisce i casi quando la destinazione del clic viene rimossa dal DOM quando l'evento si propaga al documento.
Alice,

21

Controlla il target dell'evento click della finestra (dovrebbe propagarsi alla finestra, purché non sia catturato da nessun'altra parte) e assicurati che non sia uno degli elementi del menu. In caso contrario, sei fuori dal tuo menu.

Oppure controlla la posizione del clic e vedi se è contenuta nell'area del menu.


18

Ho avuto successo con qualcosa del genere:

var $menuscontainer = ...;

$('#trigger').click(function() {
  $menuscontainer.show();

  $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
      $menuscontainer.hide();
    }
  });
});

La logica è: quando #menuscontainerviene mostrato, associare un gestore di clic al corpo che si nasconde #menuscontainersolo se il bersaglio (del clic) non è figlio di esso.


17

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

Non ha alcun problema con l' interruzione della propagazione degli eventi e supporta meglio più menu nella stessa pagina in cui facendo clic su un secondo menu mentre un primo è aperto lascerà il primo aperto nella soluzione stopPropagation.


16

L'evento ha una proprietà chiamata event.path dell'elemento che è un "elenco ordinato statico di tutti i suoi antenati in ordine di struttura" . Per verificare se un evento ha avuto origine da un elemento DOM specifico o da uno dei suoi figli, basta controllare il percorso per quell'elemento DOM specifico. Può anche essere usato per controllare più elementi inserendo logicamente ORil controllo degli elementi nella somefunzione.

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

Quindi per il tuo caso dovrebbe essere

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});

1
Non funziona in Edge.
Legends,

event.pathnon è una cosa.
Andrew,

14

Ho trovato questo metodo in alcuni plugin del calendario 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);

13

Ecco la soluzione JavaScript vaniglia per i futuri spettatori.

Facendo clic su qualsiasi elemento all'interno del documento, se l'id dell'elemento cliccato viene attivato o disattivato o l'elemento nascosto non è nascosto e l'elemento nascosto non contiene l'elemento cliccato, attivare o disattivare l'elemento.

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

Se hai più interruttori nella stessa pagina puoi usare qualcosa del genere:

  1. Aggiungi il nome della classe hiddenall'elemento comprimibile.
  2. Al clic del documento, chiudere tutti gli elementi nascosti che non contengono l'elemento cliccato e non sono nascosti
  3. Se l'elemento selezionato è un interruttore, attiva o disattiva l'elemento specificato.

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


13

Sono sorpreso che nessuno abbia effettivamente riconosciuto focusout evento:

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>



Questa dovrebbe essere la risposta accettata, direttamente al punto. Grazie!!!
Lucky Lam,

8

Se stai scrivendo script per IE e FF 3. * e vuoi solo sapere se il clic si è verificato all'interno di una determinata area della casella, puoi anche usare qualcosa come:

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

8

Invece usando l'interruzione del flusso, l'evento di sfocatura / messa a fuoco o qualsiasi altra tecnica complicata, abbina semplicemente il flusso di eventi alla parentela dell'elemento:

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

Per rimuovere il clic al di fuori del listener di eventi, è sufficiente:

$(document).off("click.menu-outside");

Per me questa è stata la soluzione migliore. L'ho usato in un callback dopo l'animazione, quindi avevo bisogno dell'evento di distacco da qualche parte. +1
qwertzman,

C'è un controllo ridondante qui. se l'elemento è davvero #menuscontainerstai ancora attraversando i suoi genitori. dovresti prima controllarlo, e se non è quell'elemento, allora vai su l'albero del DOM.
vsync,

Giusto ! È possibile modificare la condizione in if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. È un'ottimizzazione minuscola, ma si verifica solo poche volte nella durata del programma: per ogni clic, se l' click.menu-outsideevento è registrato. È più lungo (+32 caratteri) e non usare il metodo di concatenamento
mems

8

Uso:

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

7

Se qualcuno curioso qui è la soluzione JavaScript (es6):

window.addEventListener('mouseup', e => {
        if (e.target != yourDiv && e.target.parentNode != yourDiv) {
            yourDiv.classList.remove('show-menu');
            //or yourDiv.style.display = 'none';
        }
    })

ed es5, nel caso in cui:

window.addEventListener('mouseup', function (e) {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
    yourDiv.classList.remove('show-menu'); 
    //or yourDiv.style.display = 'none';
}

});


7

Ecco una semplice soluzione di javascript puro. È aggiornato con 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;
})

"Aggiornato con ES6" è un'affermazione piuttosto audace, quando l'unica cosa aggiornata con ES6 sta facendo () => {}invece di function() {}. Quello che hai lì è classificato come semplice JavaScript con un tocco di ES6.
MortenMoulder,

@MortenMoulder: Ya. È solo per attenzione, anche se in realtà è ES6. Ma guarda la soluzione. Penso che sia buono
Duannx,

È JS vaniglia e funziona per il target dell'evento rimosso dal DOM (ad es. Quando viene selezionato il valore dal popup interno, chiudendo immediatamente il popup). +1 da me!
Alice,

6

Agganciare un listener di eventi clic sul documento. All'interno del listener di eventi, puoi guardare l' oggetto evento , in particolare event.target per vedere quale elemento è stato cliccato:

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

6

Ho usato sotto lo script e fatto con 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
    }
})

Di seguito trovi il codice 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>

Puoi leggere il tutorial qui


5
$(document).click(function() {
    $(".overlay-window").hide();
});
$(".overlay-window").click(function() {
    return false;
});

Se si fa clic sul documento, nascondere un determinato elemento, a meno che non si faccia clic sullo stesso elemento.


5

Ottieni la risposta più popolare, ma aggiungi

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

quindi, un clic su una barra di scorrimento non [nasconde o altro] l'elemento target.


5

Per un uso più semplice e un codice più espressivo, ho creato un plug-in jQuery per questo:

$('div.my-element').clickOut(function(target) { 
    //do something here... 
});

Nota: target è l'elemento su cui l'utente ha effettivamente fatto clic. Ma callback viene ancora eseguito nel contesto del elemento originale, in modo da poter utilizzare questo come ci si aspetterebbe in un callback jQuery.

Collegare:

$.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;
};

Per impostazione predefinita, il listener di eventi click viene inserito nel documento. Tuttavia, se si desidera limitare l'ambito del listener di eventi, è possibile passare un oggetto jQuery che rappresenta un elemento di livello padre che sarà il principale padre in cui verranno ascoltati i clic. Questo impedisce listener di eventi a livello di documento non necessari. Ovviamente, non funzionerà se l'elemento genitore fornito non è un genitore del tuo elemento iniziale.

Usa così:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.