onclick () и onblur () проблема заказа

105

У меня есть поле ввода, которое вызывает настраиваемое раскрывающееся меню. Хотелось бы следующего функционала:

  • Когда пользователь щелкает где-нибудь за пределами поля ввода, меню должно быть удалено.
  • Если, более конкретно, пользователь щелкает div внутри меню, меню должно быть удалено, и должна происходить специальная обработка в зависимости от того, какой div был нажат.

Вот моя реализация:

Поле ввода имеет onblur()событие, которое удаляет меню (путем установки его родительского элемента innerHTMLна пустую строку) всякий раз, когда пользователь щелкает мышью за пределами поля ввода. Дивы внутри меню также имеют onclick()события, которые выполняют специальную обработку.

Проблема в том, что onclick()события никогда не срабатывают при нажатии на меню, потому что поле ввода onblur()срабатывает первым и удаляет меню, включая onclick()s!

Я решил эту проблему путем разделения дивы меню onclick()в onmousedown()и onmouseup()события и установив глобальный флаг вниз мыши , который очищается от мыши вверх, подобно тому , что было предложено в этом ответе . Поскольку onmousedown()срабатывает раньше onblur(), флаг будет установлен, onblur()если был нажат один из разделов меню, но не если где-то еще на экране. Если меню было нажато, я немедленно выхожу из него, onblur()не удаляя меню, затем жду, пока сработает кнопка, после onclick()чего я могу безопасно удалить меню.

Есть ли более элегантное решение?

Код выглядит примерно так:

<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 ''
источник

Ответы:

170

У меня была та же проблема, что и у вас, мой интерфейс разработан именно так, как вы описываете. Я решил проблему, просто заменив onClickдля пунктов меню на onMouseDown. Я больше ничего не делал; нет onMouseUp, никаких флагов. Это решило проблему, позволив браузеру автоматически изменять порядок в зависимости от приоритета этих обработчиков событий без какой-либо дополнительной работы с моей стороны.

Есть ли причина, по которой это не сработало бы и для вас?

Джонбейкерс
источник
3
Это действительно работает. Я тестировал его в FF, и он работает как шарм.
Supreme Dolphin
3
Оно работает. Похоже, onmousedownвыполняется перед onblurтестированием в Firefox, Chrome и Internet Explorer.
Tonatio
11
Стоит отметить, что это немного меняет поведение - взаимодействие щелчка затем обрабатывается при нажатии мыши, а не при поднятии. Для большинства людей это может быть нормально (в данном случае это я), но есть несколько недостатков. В частности, я часто нажимаю, а затем перетаскиваю кнопку, если я неправильно щелкнул, что предотвращает вызов onclick - если кнопка выполняет не-чистую функцию (удаление, публикация и т. Д.), Вы можете сохранить это и использовать подход флага.
Brizee
2
Что насчет доступности в тот момент, когда вы устанавливаете mouseDown вместо onClick, вы закрываете доступность для всех элементов <button>: /
ncubica
2
Ответ, указанный ниже @ ian-macfarlane, является правильным способом решения этой проблемы. В сводке ... onMouseDownс event.preventDefault() и onClickс нужным действием.
Конал Тринитротолуол Да Коста
49

onClickне следует заменять на onMouseDown.

Хотя этот подход в некоторой степени работает , это два принципиально разных события, которые имеют разные ожидания в глазах пользователя. Использование onMouseDownвместо onClickразрушит предсказуемость вашего программного обеспечения в этом случае. Таким образом, эти два события не взаимозаменяемы.

Для иллюстрации: при случайном нажатии кнопки пользователи ожидают, что смогут удерживать щелчок мыши, перетащить курсор за пределы элемента и отпустить кнопку мыши, что в конечном итоге приведет к отсутствию действий. onClickЯвляется ли это. onMouseDownне позволяет пользователю удерживать мышь, а вместо этого немедленно запускает действие, не обращаясь за помощью к пользователю. onClickстандарт, по которому мы ожидаем запускать действия на компьютере.

В этой ситуации звоните event.preventDefault()на onMouseDownмероприятие. onMouseDownпо умолчанию вызовет событие размытия и не будет делать этого при preventDefaultвызове. Тогда onClickбудет шанс , что вас позвонят. Событие размытия все равно произойдет, только после onClick.

В конце концов, onClickсобытие представляет собой комбинацию onMouseDownи onMouseUp, если и только если они оба происходят в одном элементе.

Ян Макфарлейн
источник
7
Это было идеальное решение для меня, и комментарий полностью верен - замена onMouseDown сбивает с толку пользователей. Проголосовали.
Пол Бартлетт
5
это должен быть правильный ответ. спасибо за публикацию
user1189352
1
Стоит отметить, что это также имеет некоторые незначительные побочные эффекты, например, невозможность выбрать текст, щелкнув внутри элемента. Не могу вспомнить слишком много случаев, когда это было бы проблемой, просто это немного забавно.
Рей Миясака
1
Если я использую event.preventDefault()на onMouseDownмероприятии, мое onFocusмероприятие не называется
Бэтмен
3

Заменить на onmousedownс onfocus. Таким образом, это событие будет запускаться, когда фокус находится внутри текстового поля.

Заменить на onmouseupс onblur. В тот момент, когда вы уберете фокус с текстового поля, запустится onblur.

Думаю, это то, что вам может понадобиться.

ОБНОВЛЕНИЕ :

когда вы выполняете свою функцию onfocus -> удалите классы, которые вы будете применять в onblur, и добавьте классы, которые вы хотите выполнить, onfocus

и

когда вы выполняете свою функцию onblur -> удалите классы, которые вы будете применять в onfocus, и добавьте классы, которые вы хотите выполнить onblur

Я не вижу необходимости во флаговых переменных.

ОБНОВЛЕНИЕ 2:

Вы можете использовать события onmouseout и onmouseover

onmouseover - обнаруживает, когда курсор находится над ним.

onmouseout - обнаруживает, когда курсор уходит.

ХИРА ТАКУР
источник
Я отредактировал свой вопрос для ясности. Как это решение позволяет избежать установки флага? Кроме того, я использую onmousedown()и onmouseup()в меню, а не в текстовом поле / поле ввода.
1 '21
Я пока не могу принять этот ответ, потому что не знаю, что он правильный. Не могли бы вы ответить на вопросы, которые я выразил в своем комментарии выше?
1 '26
Конечно, я вижу, как вы можете использовать onmouseover и onmouseout аналогично тому, что я делаю сейчас, чтобы установить флаг, когда мышь находится над меню. Тем не менее, я все еще думаю, что вам нужно будет установить флаг в onmouseover, который очищается в onmouseout, чтобы вы знали, были ли вы над меню при нажатии. Вы можете придумать способ, который не требует установки флага?
1 '26
Могу я увидеть ваш код ... вставьте его в скрипку ... просто вставьте соответствующую часть, и все будет в порядке, если я не увижу результатов ... просто код ...
ХИРА ТАКУР
2

Более элегантное (но, вероятно, менее производительное) решение:

Вместо использования onblur ввода для удаления меню используйте document.onclick, которое срабатывает после onblur.

Однако это также означает, что меню удаляется при нажатии на сам вход, что является нежелательным поведением. Установите input.onclickс, event.stopPropagation()чтобы избежать распространения щелчков на событие щелчка документа.

1 ''
источник
5
Что ж, проблема с этим ответом заключается в том, что это не событие щелчка, которое в конечном итоге вызывает размытие. Что, если пользователь перешел на табуляцию ввода? Это приведет к срабатыванию события размытия, но всплывающее меню все равно будет видно.
Christoph
0

изменить onclick на onfocus

даже если onblur и onclick не очень хорошо уживаются, но, очевидно, onfocus и да, onblur. поскольку даже после того, как меню закрыто, onfocus по-прежнему действует для элемента, внутри которого щелкнули.

Я сделал, и это сработало.

Бразилия
источник
-7

Вы можете использовать setIntervalфункцию внутри своего onBlurобработчика, например:

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

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

setIntervalфункция удалить onBlur функцию из стека вызовов, добавить , потому что вы установите время на 0, эта функция будет вызвана сразу же после того, как другой обработчик события закончил

user5271069
источник
5
setInterval- плохая идея, вы никогда не отменяете ее, поэтому она будет постоянно запускать вашу функцию и, скорее всего, заставит ваш сайт использовать максимальное количество ЦП. Используйте setTimeoutвместо этого, если собираетесь использовать эту технику (которую я не рекомендую). Заставлять ваше приложение зависеть от времени определенных событий - рискованное дело.
Кейси
6
ОПАСНО БУДЕТ РОБИНСОН! ОПАСНОСТЬ! Состояние гонки впереди!
GoreDefex
Первоначально я придумал это решение (ну, setTimeoutнет setInterval), но мне пришлось увеличить его до 250мс, прежде чем время произойдет в правильном порядке. Эта ошибка, вероятно, все еще будет существовать на более медленных устройствах, а также делает задержку заметной. Я попробовал, onMouseDownи он отлично работает. Интересно, нужны ли ему и события касания.
Brennan Cheung
Что-то вроде интервала неплохо, если вы действительно хотите использовать onClick. Но вам нужен рекурсивный тайм-аут с условием, а не интервал, чтобы он не работал вечно. Это тот же шаблон, который используется в условных ожиданиях Selenium.
Will Cain