Один из наиболее распространенных способов взаимодействия с компьютером — это использование мыши (рис. 33.1).
Рис. 33.1. Кошки их тоже наверняка любят
Перемещая это волшебное устройство и нажимая на его кнопки, можно совершать множество полезных действий. При этом использование мыши простым пользователем — это одно. Для разработчика же согласование работы кода с действиями мыши — уже совсем другое. Здесь мы и начнем очередную главу.
Знакомьтесь с событиями мыши
В JavaScript основным способом работы с мышью являются события. Для этой задачи предусмотрено их великое множество, но в этой теме мы не станем рассматривать все подряд, а вместо этого сфокусируемся на самых популярных:
• click
• dblclick
• mouseover
• mouseout
• mouseenter
• mouseleave
• mousedown
• mouseup
• mousemove
• contextmenu
• mousewheel и DOMMouseScroll
Названия этих событий могут уже дать некоторое представление об их назначении, но мы, тем не менее, рассмотрим каждое из них подробнее. Хочу сразу предупредить вас, что некоторые из событий ужасно скучны.
Одинарный или двойной клик
Начнем с наиболее часто используемого события мыши — клика. Это событие срабатывает, когда вы щелкаете по элементу. Иначе можно сказать, что событие click срабатывает, когда вы используете мышь для нажатия на элемент, а затем отпускаете нажатие, сохраняя курсор на этом элементе.
Ниже — абсолютно бесполезная визуализация сказанного:
Вы уже несколько раз видели код для работы с событием клика, но его никогда не бывает достаточно, поэтому вот вам еще один пример:
let button = document.querySelector("#myButton");
button.addEventListener("click", doSomething, false);
function doSomething(e) {
console.log("Mouse clicked on something!");
}
Прослушивание события click аналогично практически любому другому событию, поэтому не стану утомлять вас подробностями нашей старой знакомой функции addEventListener. Вместо этого утомлю вас подробностями, связанными с событием dblclick.
dblclick срабатывает, когда вы быстро щелкаете мышью дважды. Код для использования этого события будет следующим:
let button = document.querySelector("#myButton");
button.addEventListener("dblclick", doSomething, false);
function doSomething(e) {
console.log("Mouse clicked on something…twice!");
}
Время между кликами, определяющее срабатывание события dblclick, заложено в ОС, где происходит выполнение кода. Оно не зависит от браузера и не определяется (не считывается) пользователем с помощью JavaScript.
НЕ ПЕРЕГРУЖАЙТЕ
Если вдруг случится прослушивать и событие click, и событие dblclick для элемента, обработчики событий будут вызваны три раза при двойном щелчке. Вы получите два события щелчка, которые будут соответствовать каждому щелчку. После второго щелчка вы также получите событие dblclick.
Наведение и отведение курсора
Классические сценарии наведения и отведения курсора обрабатываются логично названными событиями mouseover и mouseout соответственно:
Вот фрагмент кода с применением этих событий:
let button = document.querySelector("#myButton");
button.addEventListener("mouseover", hovered, false);
button.addEventListener("mouseout", hoveredOut, false);
function hovered(e) {
console.log("Hovered!");
}
function hoveredOut(e) {
console.log("Hovered Away!");
}
Это все, что касается этих событий. По большому счету, они весьма скучны, что, как вы уже наверняка поняли, даже хорошо, когда дело касается принципов программирования.
Что насчет двух других похожих событий?
Мы рассмотрели только два события (mouseover и mouseout), которые срабатывают при наведении курсора на что-либо и его отведении. На деле же оказывается, что есть еще два события, которые делают то же самое, — mouseenter и mouseleave. Уникальность этих событий обусловливается одной важной деталью, а именно тем, что они не всплывают.
Это важно, только если интересующий вас элемент имеет потомков. Все эти четыре события ведут себя одинаково, если в процессе не присутствуют потомки. Если же таковые присутствуют, тогда:
• mouseover и mouseout будут срабатывать каждый раз, когда вы наводите курсор на потомка. Это значит, что можно увидеть срабатывание многих ненужных событий, несмотря на то что курсор двигается внутри одной области.
• mouseenter и mouseleave будут срабатывать только единожды. При этом не важно, через сколько потомков вы переместите курсор мыши.
В 90 % случаев вам вполне подойдут mouseover и mouseout. В остальных случаях, которые зачастую связаны с более сложными сценариями UI, вас порадует, что существуют такие события, как mouseenter и mouseleave.
События mousedown и mouseup
Два события, которые практически являются субкомпонентами события click, — это mousedown и mouseup. Следующая диаграмма поясняет почему:
Когда вы нажимаете на кнопку мыши, срабатывает событие mousedown. Когда вы отпускаете нажатие, срабатывает событие mouseup. Если нажатие и отпускание произошло на одном и том же элементе, тогда также сработает событие click.
Все это показано в следующем фрагменте кода:
let button = document.querySelector("#myButton");
button.addEventListener("mousedown", mousePressed, false);
button.addEventListener("mouseup", mouseReleased, false);
button.addEventListener("click", mouseClicked, false);
function mousePressed(e) {
console.log("Mouse is down!");
}
function mouseReleased(e) {
console.log("Mouse is up!");
}
function mouseClicked(e) {
console.log("Mouse is clicked!");
}
Справедливый вопрос: «Зачем заморачиваться этими двумя событиями?» Кажется, что событие click идеально подходит для большинства случаев, в которых может понадобиться использовать mousedown и mouseup. Вы правы, можно не замарачиваться. При этом будет полезно пояснить, что события mousedown и mouseup просто дают больше контроля, когда он нужен. Некоторые взаимодействия (вроде перетаскиваний или отпадных приемов в видеоиграх, когда вы задерживаете нажатие, чтобы зарядить смертоносный удар молнии) подразумевают совершение действий, когда сработало только событие mousedown, но не mouseup.
Событие услышано снова… и снова… и снова!
Одним из самых болтливых событий, с которыми вам предстоит работать, является событие mousemove. Оно срабатывает огромное количество раз во время движения курсора по элементу, в котором вы прослушиваете событие mousemove:
Далее приведен пример использования mousemove в коде:
let button = document.querySelector("#myButton");
button.addEventListener("mousemove", mouseIsMoving, false);
function mouseIsMoving(e) {
console.log("Mouse is on the run!");
}
Ваш браузер контролирует частоту, с которой срабатывает событие mousemove, при этом оно срабатывает, когда курсор мыши смещается даже всего на один пиксель. Это событие хорошо для многих интерактивных сценариев, в которых, к примеру, важно отслеживать текущую позицию курсора.
Контекстное меню
Последним связанным с мышью событием, которое мы рассмотрим, является contextmenu. Как вам наверняка хорошо известно, когда вы по обыкновению кликаете правой кнопкой мыши в различных приложениях, появляется меню:
Оно называется контекстное меню. Как раз перед появлением этого меню срабатывает событие contextmenu.
Честно говоря, есть всего одна весомая причина для прослушивания этого события. Она связана с предотвращением появления этого меню при правом клике, использовании связанной с ним клавиши клавиатуры или просто горячей клавиши.
Вот пример того, как вы можете предотвратить встроенное поведение, при котором появляется контекстное меню:
document.addEventListener("contextmenu", hideMenu, false);
function hideMenu(e) {
e. preventDefault();
}
Метод preventDefault в любом типе Event предотвращает любое его встроенное действие. Так как событие contextmenu срабатывает до появления меню, вызов preventDefault гарантирует, что оно показано не будет. Да, я уже второй раз упоминаю это свойство, но вы же знаете, что мне платят за количество символов (ха-ха).
Учитывая все сказанное, я могу придумать множество альтернативных способов для предотвращения появления контекстного меню без помощи событий, но пока что дела обстоят именно так.
Свойства MouseEvent
Давайте перейдем к конкретике. Все события мыши, которые мы видели до сих пор, основаны на MouseEvent. Обычно эту разновидность фактоида[4] вы храните исключительно для торжественных случаев и игнорируете. Тем не менее в данном случае эта деталь для нас важна, так как MouseEvent несет в себе набор свойств, упрощающих работу с мышью. Давайте на них посмотрим.
Глобальная позиция мыши
Свойства screenX и screenY возвращают расстояние, на котором находится курсор мыши от левого верхнего угла основного монитора:
Вот очень простой пример использования screenX и screenY:
document.addEventListener("mousemove", mouseMoving, false);
function mouseMoving(e) {
console.log(e.screenX + " " + e.screenY);
}
При этом не важно, какие еще действия с отступами, заполнением или сдвигами макета производятся на странице. Возвращаемые значения всегда будут отражать расстояние между текущим положением курсора и левым верхним углом основного монитора.
Позиция курсора мыши в браузере
Свойства clientX и clientY возвращают позиции x и y курсора относительно левого верхнего угла браузера (технически его области просмотра):
Код в данном случае достаточно прост:
let button = document.querySelector("#myButton");
document.addEventListener("mousemove", mouseMoving, false);
function mouseMoving(e) {
console.log(e.clientX + " " + e.clientY);
}
Вы просто вызываете свойства clientX и clientY аргумента события, переданного в обработчик события, чтобы получить их значения.
Определение нажатой кнопки
Мыши зачастую оборудованы несколькими кнопками или предоставляют возможность их имитировать. Наиболее распространенная конфигурация состоит из левой, правой и средней (обычно это нажатие на колесико мыши) кнопок. Для определения, какая из кнопок была нажата, существует свойство button. Это свойство возвращает 0 при нажатии левой кнопки, 1 — при нажатии средней и 2 — при нажатии правой:
Код для использования этого свойства выглядит вполне ожидаемо:
document.addEventListener("mousedown", buttonPress, false);
function buttonPress(e) {
if (e.button == 0) {
console.log("Left mouse button pressed!");
} else if (e.button == 1) {
console.log("Middle mouse button pressed!");
} else if (e.button == 2) {
console.log("Right mouse button pressed!");
} else {
console.log("Things be crazy up in here!!!");
}
}
В дополнение к свойству button существует свойство buttons, а также другие, которые делают нечто похожее для помощи в определении нажатой кнопки. Я не буду много говорить об этих свойствах, просто имейте в виду, что они существуют (вы всегда можете погуглить их).
Работа с колесиком мыши
Колесико мыши отличается от всего, что мы рассмотрели до этого момента. Очевидная разница в том, что здесь мы уже имеем дело с колесиком, а не кнопкой. Менее же очевидное, но при этом более важное отличие в том, что в данном случае вы используете уже два события для работы. Первое — это mousewheel, используемое в Internet Explorer и Chrome, а второе — это DOMMouseScroll, используемое в Firefox.
Прослушивание этих событий производится обычным образом:
document.addEventListener("mousewheel", mouseWheeling, false);
document.addEventListener("DOMMouseScroll", mouseWheeling, false);
А вот после уже есть нюансы. События mousewheel и DOMMouseScroll будут срабатывать в момент прокручивания колесика в любом направлении. Но для любой практической цели будет важно, в каком направлении происходит прокрутка. Чтобы получить эту информацию, пороемся в обработчике событий и найдем аргумент события.
Аргументы события для события mousewheel содержат свойство под названием wheelDelta. В случае же с DOMMouseScroll в аргументе события присутствует свойство detail. Оба этих свойства похожи в том, что их значения изменяются на положительные или отрицательные в зависимости от направления прокрутки колесика. Здесь стоит отметить, что они не согласованы в трактовке положительного и отрицательного значения. Свойство wheelDelta, связанное с событием mousewheel, становится положительным при прокрутке вверх и отрицательным при прокрутке вниз. В точности наоборот происходит в случае со свойством DOMMouseScroll. При прокрутке вверх оно дает отрицательное значение, а при прокрутке вниз — положительное.
Из следующего примера видна обработка этой несогласованности свойств wheelDelta и detail, которая весьма проста:
function mouseWheeling(e) {
let scrollDirection;
let wheelData = e.wheelDelta;
if (wheelData) {
scrollDirection = wheelData;
} else {
scrollDirection = -1 * e.detail;
}
if (scrollDirection > 0) {
console.log("Scrolling up! " + scrollDirection);
} else {
console.log("Scrolling down! " + scrollDirection);
}
}
Переменная scrollDirection хранит значение, содержащееся в свойстве wheelData или detail. Вы можете определить особое поведение в зависимости от того, является значение положительным или отрицательным.
КОРОТКО О ГЛАВНОМ
Если вы умеете работать с одним событием, то, значит, понимаете основу работы со всеми остальными. Главное — знать, какое событие соответствует нужным вам действиям. Знакомство с событиями мыши — это хорошее начало для освоения работы с событиями в принципе, так как ничего сложного в этом нет. Они не отличаются беспорядочностью, и то, что вы о них узнаете, вы будете использовать практически во всех своих приложениях.
Дополнительные ресурсы и примеры, которые могут вас заинтересовать:
• Перемещение элемента в место клика: http://bit.ly/kirupaElementClickPosition
• Вы используете сенсорное устройство? http://bit.ly/kirupaTouchEnabled
Если у вас есть какие-либо вопросы, уделите им время и обратитесь на форум https://forum.kirupa.com.