problema di ordinamento onclick () e onblur ()


102

Ho un campo di input che fa apparire un menu a discesa personalizzato. Vorrei la seguente funzionalità:

  • Quando l'utente fa clic in un punto qualsiasi al di fuori del campo di input, il menu dovrebbe essere rimosso.
  • Se, più specificamente, l'utente fa clic su un div all'interno del menu, il menu dovrebbe essere rimosso e dovrebbe avvenire un'elaborazione speciale in base a quale div è stato cliccato.

Ecco la mia implementazione:

Il campo di input ha un onblur()evento che cancella il menu (impostando il suo genitore innerHTMLsu una stringa vuota) ogni volta che l'utente fa clic al di fuori del campo di input. I div all'interno del menu hanno anche onclick()eventi che eseguono l'elaborazione speciale.

Il problema è che gli onclick()eventi non si attivano mai quando si fa clic sul menu, perché il campo di input viene onblur()attivato per primo ed elimina il menu, incluso il messaggio di posta onclick()elettronica!

Ho risolto il problema suddividendo il menu div onclick()in onmousedown()ed onmouseup()events e impostando un flag globale al mouse down che viene cancellato al mouse up, simile a quanto suggerito in questa risposta . Poiché si onmousedown()attiva prima onblur(), il flag verrà impostato onblur()se è stato fatto clic su uno dei div del menu, ma non se lo era da qualche altra parte sullo schermo. Se il menu è stato cliccato, torno immediatamente da onblur()senza eliminare il menu, quindi aspetto onclick()che si attivi, a quel punto posso eliminare in sicurezza il menu.

Esiste una soluzione più elegante?

Il codice ha un aspetto simile a questo:

<div class="menu" onmousedown="setFlag()" onmouseup="doProcessing()">...</div>
<input id="input" onblur="removeMenu()" ... />

var mouseflag;

function setFlag() {
    mouseflag = true;
}

function removeMenu() {
    if (!mouseflag) {
        document.getElementById('menu').innerHTML = '';
    }
}

function doProcessing(id, name) {
    mouseflag = false;
    ...
}

Risposte:


168

Stavo avendo lo stesso identico problema di te, la mia interfaccia utente è progettata esattamente come descrivi. Ho risolto il problema semplicemente sostituendo le onClickvoci di menu con un file onMouseDown. Non ho fatto altro; no onMouseUp, niente bandiere. Ciò ha risolto il problema consentendo al browser di riordinare automaticamente in base alla priorità di questi gestori di eventi, senza alcun lavoro aggiuntivo da parte mia.

C'è qualche motivo per cui questo non avrebbe funzionato anche per te?


3
Funziona davvero. L'ho provato in FF e funziona a meraviglia.
Supreme Dolphin

3
Funziona. Sembra che onmousedownvenga eseguito prima del onblurtest in Firefox, Chrome e Internet Explorer.
Tonatio

11
Vale la pena notare che questo cambia il comportamento in modo un po 'naturale: l'interazione del clic viene quindi gestita con il mouse in basso anziché in alto. Per la maggior parte delle persone potrebbe andare bene (auto incluso in questo caso) ma ci sono alcuni inconvenienti. In particolare, faccio spesso clic e trascino fuori il pulsante se ho fatto un clic errato, il che impedisce di essere chiamato onclick - se il pulsante esegue una funzione non pura (elimina, posta ecc.) Potresti voler preservare questo e seguire l'approccio flag.
Brizee

2
Che dire dell'accessibilità nel momento in cui imposti mouseDown invece di onClick uccidi l'accessibilità per tutti gli elementi <button>: /
ncubica

2
La risposta elencata di seguito da @ ian-macfarlane è il modo corretto di affrontare questo problema. In sintesi ... onMouseDowncon event.preventDefault() e onClickcon l'azione desiderata.
Conal Trinitrotoluene Da Costa

47

onClicknon deve essere sostituito con onMouseDown.

Sebbene questo approccio funzioni in qualche modo , i due sono eventi fondamentalmente diversi che hanno aspettative diverse agli occhi dell'utente. In questo caso, utilizzare onMouseDowninvece di onClickrovinerà la prevedibilità del software. Pertanto, i due eventi non sono intercambiabili.

Per illustrare: quando si fa clic accidentalmente su un pulsante, gli utenti si aspettano di poter tenere premuto il clic del mouse, trascinare il cursore all'esterno dell'elemento e rilasciare il pulsante del mouse, senza che ciò comporti alcuna azione. onClickfa questo. onMouseDownnon consente all'utente di tenere premuto il mouse e invece attiverà immediatamente un'azione, senza alcun ricorso per l'utente. onClickè lo standard con cui ci aspettiamo di attivare azioni su un computer.

In questa situazione, chiama event.preventDefault()l' onMouseDownevento. onMouseDowncauserà un evento di sfocatura per impostazione predefinita e non lo farà quando preventDefaultviene chiamato. Quindi, onClickavrà la possibilità di essere chiamato. Un evento sfocato accadrà ancora, solo dopo onClick.

Dopo tutto, l' onClickevento è una combinazione di onMouseDowne onMouseUp, se e solo se si verificano entrambi all'interno dello stesso elemento.


7
Questa era la soluzione perfetta per me e il commento è totalmente corretto: la sostituzione di onMouseDown crea confusione per l'esperienza dell'utente. Hanno votato.
Paul Bartlett

5
questa dovrebbe essere la risposta corretta. grazie per aver postato questo
user1189352

1
Vale la pena notare che questo ha anche alcuni effetti collaterali minori, ad esempio non essere in grado di selezionare il testo facendo clic all'interno dell'elemento. Non riesco a pensare a troppi casi in cui questo potrebbe essere un problema, solo che sembra un po 'divertente.
Rei Miyasaka

1
Se utilizzo event.preventDefault()su un onMouseDownevento, il mio onFocusevento non si chiama
Batman,

4

Sostituisci onmousedowncon onfocus. Quindi questo evento verrà attivato quando il focus è all'interno della casella di testo.

Sostituisci onmouseupcon onblur. Nel momento in cui estrai il tuo focus dalla casella di testo, onblur verrà eseguito.

Immagino che questo sia ciò di cui potresti aver bisogno.

AGGIORNAMENTO :

quando esegui la tua funzione onfocus -> rimuovi le classi che applicherai in onblur e aggiungi le classi che vuoi che vengano eseguite onfocus

e

quando esegui la tua funzione onblur -> rimuovi le classi che applicherai in onfocus e aggiungi le classi che vuoi che vengano eseguite su blur

Non vedo alcuna necessità di variabili flag.

AGGIORNAMENTO 2:

Puoi usare gli eventi onmouseout e onmouseover

onmouseover -Rileva quando il cursore si trova su di esso.

onmouseout -Rileva quando il cursore esce.


Ho modificato la mia domanda per chiarezza. In che modo questa soluzione evita la necessità di impostare una bandiera? Inoltre, io uso onmousedown()e onmouseup()nel menu, non la casella di testo / campo di input.
1 ''

Non posso ancora accettare questa risposta perché non so se sia corretta. Potresti rispondere alle preoccupazioni che ho sollevato nel mio commento sopra?
1 ''

Certo, posso vedere come potresti usare onmouseover e onmouseout in modo simile a quello che sto facendo attualmente, per impostare un flag quando il mouse si trova sul menu. Tuttavia, penso ancora che dovresti impostare un flag in onmouseover che viene cancellato in onmouseout, in modo che tu possa sapere se eri sopra il menu quando hai fatto clic. Riesci a pensare a un modo che non richieda l'impostazione di una bandiera?
1 ''

Posso vedere il tuo codice ... metterlo in un violino ... metti solo la parte rilevante ed è completamente ok se non vedo i risultati ... solo il codice ..
HIRA THAKUR


2

Una soluzione più elegante (ma probabilmente meno performante):

Invece di usare onblur dell'input per rimuovere il menu, usa document.onclick, che si attiva dopo onblur.

Tuttavia, ciò significa anche che il menu viene rimosso quando si fa clic sull'input stesso, il che è un comportamento indesiderato. Impostare un input.onclickcon event.stopPropagation()per evitare di propagare i clic all'evento clic del documento.


5
Bene, il problema con questa risposta, però, è se non fosse un evento clic che ha finito per innescare la sfocatura. Cosa succede se l'utente esce dall'input? Ciò provocherebbe l'attivazione dell'evento di sfocatura ma il menu a comparsa sarebbe ancora visibile.
Christoph

0

cambia al clic di onfocus

anche se onblur e onclick non vanno molto d'accordo, ma ovviamente onfocus e sì onblur. poiché anche dopo la chiusura del menu l'onfocus è ancora valido per l'elemento cliccato all'interno.

L'ho fatto e ha funzionato.


-7

Puoi usare una setIntervalfunzione all'interno del tuo onBlurgestore, come questa:

<input id="input" onblur="removeMenu()" ... />

function removeMenu() {
    setInterval(function(){
        if (!mouseflag) {
            document.getElementById('menu').innerHTML = '';
        }
    }, 0);
}

la setIntervalfunzione rimuoverà la tua onBlur funzione dallo stack di chiamate, aggiungi perché hai impostato il tempo a 0, questa funzione verrà chiamata immediatamente dopo che l'altro gestore di eventi ha finito


5
setIntervalè una cattiva idea, non la annullerai mai, quindi eseguirà continuamente la tua funzione e molto probabilmente farà sì che il tuo sito utilizzi max cpu. Usala setTimeoutinvece se intendi utilizzare questa tecnica (che non consiglio). Fare in modo che la tua app dipenda dalla tempistica di determinati eventi è un'attività rischiosa.
casey

6
PERICOLO SARÀ ROBINSON! PERICOLO! Condizioni di gara avanti!
GoreDefex

Inizialmente ho escogitato questa soluzione (beh, setTimeoutno setInterval) ma ho dovuto aumentarla fino a 250ms prima che i tempi avvengano nell'ordine corretto. Questo bug probabilmente esisterà ancora sui dispositivi più lenti e inoltre rende evidente il ritardo. Ho provato onMouseDowne funziona bene. Mi chiedo se abbia bisogno anche degli eventi touch.
Brennan Cheung

Qualcosa come un intervallo non è male se vuoi davvero usare onClick. Ma vorresti un timeout ricorsivo con una condizione piuttosto che un intervallo in modo che non venga eseguito per sempre. Questo è lo stesso modello utilizzato dalle attese condizionali Selenio.
Will Cain
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.