Problem z zamówieniem onclick () i onblur ()

102

Mam pole wprowadzania, które wyświetla niestandardowe menu rozwijane. Chciałbym mieć następującą funkcjonalność:

  • Gdy użytkownik kliknie gdziekolwiek poza polem wejściowym, menu powinno zostać usunięte.
  • Jeśli, dokładniej, użytkownik kliknie element div w menu, menu powinno zostać usunięte i powinno nastąpić specjalne przetwarzanie w zależności od tego, który element div został kliknięty.

Oto moja realizacja:

Pole wejściowe ma onblur()zdarzenie, które usuwa menu (ustawiając jego innerHTMLelement nadrzędny na pusty ciąg), gdy użytkownik kliknie poza polem wejściowym. Elementy div w menu mają również onclick()zdarzenia, które wykonują specjalne przetwarzanie.

Problem polega na tym, że onclick()zdarzenia nigdy nie są uruchamiane po kliknięciu menu, ponieważ pole wejściowe onblur()uruchamia się jako pierwsze i usuwa menu, w tym onclick()s!

I rozwiązać problem dzielenia DIV menu onclick()do onmousedown()i onmouseup()wydarzenia i ustawienie globalną flagę na myszy, która jest wydalana na mysz w górę, podobny do tego, co było sugerowane w tej odpowiedzi . Ponieważ onmousedown()uruchamia się wcześniej onblur(), flaga zostanie ustawiona, onblur()jeśli kliknięto jeden z elementów div menu, ale nie, jeśli gdzieś indziej na ekranie. Jeśli menu zostało kliknięte, natychmiast wracam z niego onblur()bez usuwania menu, a następnie czekam na onclick()uruchomienie, po czym mogę bezpiecznie usunąć menu.

Czy jest bardziej eleganckie rozwiązanie?

Kod wygląda mniej więcej tak:

<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;
    ...
}
1 ''
źródło

Odpowiedzi:

168

Miałem dokładnie ten sam problem co Ty, mój interfejs użytkownika jest zaprojektowany dokładnie tak, jak opisujesz. Rozwiązałem problem, po prostu zastępując onClickdla pozycji menu rozszerzenie onMouseDown. Nic innego nie zrobiłem; nie onMouseUp, żadnych flag. To rozwiązało problem, pozwalając przeglądarce automatycznie zmieniać kolejność na podstawie priorytetu tych programów obsługi zdarzeń, bez żadnej dodatkowej pracy z mojej strony.

Czy jest jakiś powód, dla którego to również nie zadziałałoby dla Ciebie?

johnbakers
źródło
3
To faktycznie działa. Przetestowałem to w FF i działa jak urok.
Supreme Dolphin
3
To działa. Wygląda na to, że onmousedownjest wykonywany przed onblurtestem w przeglądarce Firefox, Chrome i Internet Explorer.
Tonatio,
11
Warto zauważyć, że zmienia to zachowanie nieco naturalnie - interakcja z kliknięciem jest wtedy obsługiwana po naciśnięciu myszy zamiast myszy w górę. Dla większości ludzi może to być w porządku (w tym przypadku dotyczy to własnej osoby), ale ma kilka wad. Przede wszystkim często klikam, a następnie przeciągam przycisk, jeśli źle kliknąłem, co zapobiega wywołaniu funkcji onclick - jeśli przycisk wykonuje funkcję inną niż Pure (usuń, opublikuj itp.), Możesz chcieć to zachować i zastosować podejście flagowe.
Brizee
2
A co z dostępnością w momencie ustawienia mouseDown zamiast onClick zabijasz dostępność dla wszystkich elementów <button>: /
ncubica
2
Odpowiedź podana poniżej przez @ ian-macfarlane jest właściwym sposobem rozwiązania tego problemu. Podsumowując ... onMouseDownze event.preventDefault() i onClickze pożądanego działania.
Conal Trinitrotoluene Da Costa
47

onClicknie należy zastępować onMouseDown.

Chociaż to podejście w pewnym stopniu działa , są to zasadniczo różne zdarzenia, które mają różne oczekiwania w oczach użytkownika. W takim przypadku używanie onMouseDownzamiast onClickzrujnuje przewidywalność oprogramowania. Zatem te dwa zdarzenia są niezamienne.

Aby to zilustrować: po przypadkowym kliknięciu przycisku użytkownicy oczekują, że będą w stanie przytrzymać kliknięcie myszą, przeciągnąć kursor poza element i zwolnić przycisk myszy, co ostatecznie nie spowoduje żadnego działania. onClickrobi to. onMouseDownnie pozwala użytkownikowi na przytrzymanie przycisku myszy, a zamiast tego natychmiast wyzwoli akcję, bez żadnej możliwości dla użytkownika. onClickto standard, według którego spodziewamy się wyzwalania działań na komputerze.

W takiej sytuacji zadzwoń event.preventDefault()do onMouseDownwydarzenia. onMouseDownspowoduje domyślnie rozmycie i nie zrobi tego po preventDefaultwywołaniu. Wtedy onClickbędzie miał szansę zadzwonić. Rozmycie będzie nadal miało miejsce, dopiero po onClick.

W końcu onClickzdarzenie jest połączeniem onMouseDowni onMouseUp, wtedy i tylko wtedy, gdy oba występują w tym samym elemencie.

Ian MacFarlane
źródło
7
To było dla mnie idealne rozwiązanie, a komentarz jest całkowicie poprawny - zastąpienie onMouseDown jest mylące dla wygody użytkownika. Głosowałem.
Paul Bartlett
5
to powinna być prawidłowa odpowiedź. dziękuję za opublikowanie tego
user1189352
1
Warto zauważyć, że ma to również pewne drobne skutki uboczne, np. Brak możliwości zaznaczenia tekstu przez kliknięcie w elemencie. Nie mogę wymyślić zbyt wielu przypadków, w których byłby to problem, tylko że wydaje się to trochę zabawne.
Rei Miyasaka
1
Jeśli korzystam event.preventDefault()z onMouseDownwydarzenia, moje onFocuswydarzenie nie zostanie wywołane
Batman
4

Wymienić na onmousedownz onfocus. Więc to zdarzenie zostanie wyzwolone, gdy fokus znajdzie się wewnątrz pola tekstowego.

Wymienić na onmouseupz onblur. W chwili, gdy wyłączysz fokus z pola tekstowego, uruchomi się onblur.

Myślę, że tego możesz potrzebować.

AKTUALIZACJA :

kiedy wykonujesz swoją funkcję onfocus -> usuń klasy, które zastosujesz w onblur i dodaj klasy, które chcesz wykonać onfocus

i

kiedy wykonujesz swoją funkcję onblur -> usuń klasy, które zastosujesz w onfocus i dodaj klasy, które chcesz wykonać onblur

Nie widzę potrzeby stosowania zmiennych flag.

AKTUALIZACJA 2:

Możesz używać wydarzeń onmouseout i onmouseover

onmouseover - Wykrywa, kiedy kursor jest nad nim.

onmouseout - Wykrywa, kiedy kursor wychodzi.

HIRA THAKUR
źródło
Zredagowałem moje pytanie dla jasności. W jaki sposób to rozwiązanie pozwala uniknąć konieczności ustawiania flagi? Ponadto używam onmousedown()i onmouseup()w menu, a nie pola tekstowego / pola wprowadzania.
1 ''
Nie mogę jeszcze zaakceptować tej odpowiedzi, ponieważ nie wiem, czy jest poprawna. Czy mógłbyś odpowiedzieć na obawy, które poruszyłem w moim komentarzu powyżej?
1 ''
Jasne, widzę, jak można użyć onmouseover i onmouseout podobnie do tego, co obecnie robię, aby ustawić flagę, gdy kursor myszy znajduje się nad menu. Jednak nadal uważam, że musiałbyś ustawić flagę w onmouseover, która jest wyczyszczona w onmouseout, abyś wiedział, czy kliknąłeś nad menu. Czy możesz wymyślić sposób, który nie wymaga ustawiania flagi?
1 ''
Czy mogę zobaczyć kod ... włóż go w skrzypce ... po prostu włóż odpowiednią część i jest całkowicie w porządku, jeśli nie widzę wyników ...
dostosuj
niech nam kontynuować tę dyskusję w czacie
HIRA thakur
2

Bardziej eleganckie (ale prawdopodobnie mniej wydajne) rozwiązanie:

Zamiast używać onblur wejścia do usuwania menu, użyj document.onclick, który uruchamia się po onblur.

Jednak oznacza to również, że menu jest usuwane po kliknięciu samego wejścia, co jest niepożądanym zachowaniem. Ustaw wartość input.onclickz, event.stopPropagation()aby uniknąć propagowania kliknięć do zdarzenia kliknięcia dokumentu.

1 ''
źródło
5
Cóż, problem z tą odpowiedzią polega jednak na tym, że jeśli nie było to kliknięcie, które spowodowało rozmycie. Co się stanie, jeśli użytkownik opuści dane wejściowe? Spowodowałoby to zdarzenie rozmycia, ale menu wysuwane nadal byłoby widoczne.
Christoph
0

zmienić onclick przez onfocus

nawet jeśli onblur i onclick nie układają się zbyt dobrze, ale oczywiście onfocus i tak onblur. ponieważ nawet po zamknięciu menu onfocus nadal obowiązuje dla elementu klikniętego w środku.

Zrobiłem i zadziałało.

Brasil
źródło
-7

Możesz użyć setIntervalfunkcji wewnątrz swojego onBlurhandlera, na przykład:

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

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

setIntervalfunkcja usunie swoją onBlur funkcję się od stosu wywołań, dodać bo ustawisz czas 0, ta funkcja zostanie wywołana po zakończeniu drugiej niezwłocznie obsługi zdarzeń

user5271069
źródło
5
setIntervalto zły pomysł, nigdy go nie anulujesz, więc będzie on stale uruchamiał twoją funkcję i najprawdopodobniej spowoduje, że Twoja witryna będzie używać maksymalnego procesora. Użyj setTimeoutzamiast tego, jeśli zamierzasz używać tej techniki (której nie polecam). Uzależnienie aplikacji od czasu określonych zdarzeń jest ryzykowne.
casey
6
NIEBEZPIECZEŃSTWO BĘDZIE ROBINSON! ZAGROŻENIE! Stan wyścigu przed nami!
GoreDefex
Pierwotnie wymyśliłem to rozwiązanie (cóż, setTimeoutnie setInterval), ale musiałem podbić je do 250ms, zanim synchronizacja nastąpi we właściwej kolejności. Ten błąd prawdopodobnie nadal będzie istniał na wolniejszych urządzeniach, a także powoduje, że opóźnienie jest zauważalne. Wypróbowałem onMouseDowni działa dobrze. Zastanawiam się, czy potrzebuje również wydarzeń dotykowych.
Brennan Cheung
Coś w rodzaju interwału nie jest złe, jeśli naprawdę chcesz używać onClick. Ale chcesz rekurencyjnego limitu czasu z warunkiem, a nie interwałem, aby nie działał wiecznie. To jest ten sam wzorzec używany przez warunkowe oczekiwanie Selenium.
Will Cain