
Обработка колесика мыши на JavaScript
Сейчас трудно представить мышь без хотя бы одного колесика. Оно используется для прокрутки страницы, для изменения масштаба каких-либо элементов, листания списков и других действий. В основном полная обработка колесика выполняется только в стационарных приложениях, а на сайтах дело ограничивается штатными средствами браузеров. Мы не будем отставать от прогресса, и сегодня разберем как грамотно обрабатывать колесико мыши в современных web-приложениях. Начинаем с теории.
Обработчик события - функция на JavaScript, которая назначается некоторой паре "объект страницы" + "название события". Когда для объекта происходит назначенное событие, выполняются все обработчики, назначенные этому объекту.
Вращение колесика мышки - это тоже событие, значит его тоже можно обработать. Но тут есть особенность: в разных браузерах событие вращения колесика обозначается по-разному. Причину этого я объяснить не могу, впрочем как и многие другие танцы с бубнами для достижения полноценной кроссбраузерности. Сперва напишем функции установки и отмены события для любого элемента страницы, причем сделаем их универсальными, они нам еще не раз пригодятся в будущем. В браузере Internet Explorer обработчики добавляются функцией attachEvent, а названия событий имеют вид 'on'+событие, например, 'onclick', 'onload', 'onmousewheel' и т.д. В браузерах на движке Gecko (Mozilla, Firefox), на движке WebKit (Chrome, Safari) и Opera обработчики добавляются функцией addEventListener, а названия событий никак не меняются, то есть 'scroll', 'keypress', 'mousewheel'. И отдельно в этом списке стоит имя события от колесика мышки для браузеров на движке Gecko, оно единственное и уникальное - 'DOMMouseScroll'. Теперь попробуем все эти данные собрать в кучу.
Получилась вот такая универсальная кроссбраузерная функция установки обработчика события для любого элемента на странице:
Code (JavaScript) : Убрать нумерацию
- //------------------------------------------------------------------
- // Функция установки обработчика события
- // Параметры вызова:
- // hElem - DOM-элемент или его ID
- // eventName - событие
- // callback - функция, которая будет вызвана при событии
- // На выходе:
- // TRUE - обработчик установлен
- // FALSE - элемент не найден или браузер не поддерживает события
- //------------------------------------------------------------------
- function hookEvent(hElem, eventName, callback) {
- if (typeof(hElem) == 'string') {
- // Если передан ID, то получить DOM-элемент
- hElem = document.getElementById(hElem);
- }
- // Если такого элемента нет, то возврат с ошибкой
- if (!hElem) { return false; }
- if (hElem.addEventListener) {
- if (eventName == 'mousewheel') {
- // Событие вращения колесика для Mozilla
- hElem.addEventListener('DOMMouseScroll', callback, false);
- }
- // Колесико для Opera, WebKit-based, а также любые другие события
- // для всех браузеров кроме Internet Explorer
- hElem.addEventListener(eventName, callback, false);
- }
- else if (hElem.attachEvent) {
- // Все события для Internet Explorer
- hElem.attachEvent('on' + eventName, callback);
- }
- else { return false; }
- return true;
- }
Функция снятия обработчика события по функционалу практически полностью повторяет функцию установки, за исключением того, что в ней используются функции detachEvent и removeEventListener. Каждое событие отменяется индивидуально.
Code (JavaScript) : Убрать нумерацию
- //------------------------------------------------------------------
- // Функция снятия обработчика события
- // Параметры вызова:
- // hElem - DOM-элемент или его ID
- // eventName - событие
- // callback - функция обработки события, которую надо отменить
- //------------------------------------------------------------------
- function unhookEvent(hElem, eventName, callback) {
- if (typeof(hElem) == 'string') {
- // Если передан ID, то получить DOM-элемент
- hElem = document.getElementById(hElem);
- }
- // Если такого элемента нет, то возврат с ошибкой
- if (!hElem) { return false; }
- if (hElem.removeEventListener) {
- if (eventName == 'mousewheel') {
- // Событие вращения колесика для Mozilla
- hElem.removeEventListener('DOMMouseScroll', callback, false);
- }
- // Колесико для Opera, WebKit-based, а также любые другие события
- // для всех браузеров кроме Internet Explorer
- hElem.removeEventListener(eventName, callback, false);
- }
- else if (hElem.detachEvent) {
- // Все события для Internet Explorer
- hElem.detachEvent('on' + eventName, callback);
- }
- else { return false; }
- return true;
- }
Internet Explorer (wheelDelta) : 120
Firefox (detail) : -3
Google Chrome (wheelDelta): 120
Старые версии Chrome (wheelDelta): 12000
Safari (wheelDelta): 120
Opera (detail) : -3 / (wheelDelta): 120
Опять же для меня остается загадкой логика и несогласованность разработчиков различных браузеров. Придется учитывать эти особенности в нашей функции обработчика. И остается последний момент. Все функции событий обрабатываются последовательно, они как бы "всплывают". Это значит, что после выполнения нашего обработчика колесика мыши управление получат следующие или же обработчики по умолчанию, то есть страница или фрейм прокрутятся как обычно. Чтобы подавить подобные действия браузера по умолчанию, надо сделать так, чтобы наш обработчик события стал завершающим в цепочке. Для этого напишем вспомогательную функцию, которая будет вызываться в самом конце нашего обработчика, и будет подавлять или отменять все последующие обработчики. Входной параметр для нее - объект события. Функция универсальная, подходит для любых событий, в ней учтены особенности всех браузеров.
Code (JavaScript) : Убрать нумерацию
- //------------------------------------------------------------------
- // Кроссбраузерная функция подавления события
- //------------------------------------------------------------------
- function cancelEvent(e) {
- e = e ? e : window.event;
- if (e.stopPropagation) {
- e.stopPropagation();
- }
- if (e.preventDefault) {
- e.preventDefault();
- }
- e.cancelBubble = true;
- e.cancel = true;
- e.returnValue = false;
- return false;
- }
Code (JavaScript) : Убрать нумерацию
- //------------------------------------------------------------------
- // Пример функции обработки колесика мыши
- //------------------------------------------------------------------
- function MouseWheelFunction(e) {
- e = e ? e : window.event;
- // Получить элемент, на котором произошло событие
- var wheelElem = e.target ? e.target : e.srcElement;
- // Получить значение поворота колесика мыши
- var wheelData = e.detail ? e.detail * -1 : e.wheelDelta / 40;
- // В движке WebKit возвращается значение в 100 раз больше
- if (Math.abs(wheelData)>100) { wheelData=Math.round(wheelData/100); }
- // Теперь в переменной wheelElem содержится элемент, который получил
- // собщение от колесика мыши, а в wheelData значение поворота колеса
- ...
- Ваш обработчик колесика мыши
- ...
- // Отменить штатные действия браузера при кручении колеса мыши
- return cancelEvent(e);
- }
Просмотров: 21107 | Комментариев: 8
Метки: JavaScript

Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
Саша
(11.09.2010 в 16:30):
Спасибо

me@safron.su
(16.05.2010 в 17:43):
Спасибо, отличная статья!
Немного переделал под стиль jQuery:
$.fn.bindWheelEvent = function(callback) {
if(!$.isFunction(callback)) { return this; }
if(this.length == 0) { return this; }
var jsThis = this.get(0);
var normalizedCallback = function(e) {
if(!e) { var e = window.event; }
e.cancelDefaultAction = function() {
if (this.stopPropagation) {
this.stopPropagation();
}
if (this.preventDefault) {
this.preventDefault();
}
this.cancelBubble = true;
this.cancel = true;
this.returnValue = false;
return false;
}
// Получить значение поворота колесика мыши
var wheelData = e.detail ? e.detail * -1 : e.wheelDelta / 40;
// В движке WebKit возвращается значение в 100 раз больше
if (Math.abs(wheelData)>100) { wheelData=Math.round(wheelData/100); }
e.wheelData = wheelData;
return callback.call(jsThis, e);
}
if (jsThis.addEventListener) {
// Событие вращения колесика для Mozilla
jsThis.addEventListener('DOMMouseScroll', normalizedCallback, false);
// Колесико для Opera, WebKit-based, а также любые другие события
// для всех браузеров кроме Internet Explorer
jsThis.addEventListener('mousewheel', normalizedCallback, false);
}
else if (jsThis.attachEvent) {
// событие для Internet Explorer
jsThis.attachEvent('onmousewheel', normalizedCallback);
}
return this;
};
Как пользоваться примером ниже (необходимо сначала подключить тот код):
$("#my-block").bindWheelEvent(function(evt) {
// Если надо отменить всплытие события
evt.cancelDefaultAction();
return false;
});
*selffix: примеров выше :)
Немного переделал под стиль jQuery:
$.fn.bindWheelEvent = function(callback) {
if(!$.isFunction(callback)) { return this; }
if(this.length == 0) { return this; }
var jsThis = this.get(0);
var normalizedCallback = function(e) {
if(!e) { var e = window.event; }
e.cancelDefaultAction = function() {
if (this.stopPropagation) {
this.stopPropagation();
}
if (this.preventDefault) {
this.preventDefault();
}
this.cancelBubble = true;
this.cancel = true;
this.returnValue = false;
return false;
}
// Получить значение поворота колесика мыши
var wheelData = e.detail ? e.detail * -1 : e.wheelDelta / 40;
// В движке WebKit возвращается значение в 100 раз больше
if (Math.abs(wheelData)>100) { wheelData=Math.round(wheelData/100); }
e.wheelData = wheelData;
return callback.call(jsThis, e);
}
if (jsThis.addEventListener) {
// Событие вращения колесика для Mozilla
jsThis.addEventListener('DOMMouseScroll', normalizedCallback, false);
// Колесико для Opera, WebKit-based, а также любые другие события
// для всех браузеров кроме Internet Explorer
jsThis.addEventListener('mousewheel', normalizedCallback, false);
}
else if (jsThis.attachEvent) {
// событие для Internet Explorer
jsThis.attachEvent('onmousewheel', normalizedCallback);
}
return this;
};
Как пользоваться примером ниже (необходимо сначала подключить тот код):
$("#my-block").bindWheelEvent(function(evt) {
// Если надо отменить всплытие события
evt.cancelDefaultAction();
return false;
});
*selffix: примеров выше :)

Lis
(08.11.2009 в 01:21):
Спасибо, очень пригодилось!

Баг детектед
(03.11.2009 в 20:31):
Ещё не проверял на TabMixPlus-e, но дополнения вроде "Smart Middle Click" (которое таинственно исчезло на AMO) и его продолжение в виде "Intelligent Middle Clickums" которые призваны это фиксить - с задачей на данном примере не справились. Боюсь TMP то уж подавно не справится.

ManHunter
(03.11.2009 в 20:20):
Ставишь расширение TabMix Plus для FF, там в настройках:
"Ссылки" -> "Всплывающие окна JavaScript" -> "Все всплывающие открывать во вкладках"
Других более универсальных решений нет из-за невозможности однозначно определить полезное или кривое висит на обработчике onclick.
"Ссылки" -> "Всплывающие окна JavaScript" -> "Все всплывающие открывать во вкладках"
Других более универсальных решений нет из-за невозможности однозначно определить полезное или кривое висит на обработчике onclick.

Баг детектед
(03.11.2009 в 20:16):
То, что сайт дерьмо - с этим я и не спорю, просто интересно было как побороть подобное зло и этот сайт был приведён как пример.
А не подскажешь как бы звучала регулярка для Proxomitron-a?
Подозреваю, что её можно было бы удачно конвертировать в юзерскрипт и обойтись без доп. софта.
Вместо location.href="..." могло быть и window.open("...").
Или <a onclick="loadURI('http://qwe.qwe/');">.
Или даже <a onclick="loadURI('%parent%/pahe.html');">
и <a onclick="loadURI(gBaseURI + '/pahe.html');">
Защититься от подобного хочется даже не столько из-за того, что подобный быдло-код мог быть написан от неумения писать нормальный, я допускаю возможность что оно может использоваться и намеренно: ведь может быть написана новая функция onclick="someFunc('...')"
которая бы делала то же самое, и такой вариант фиксить универсально уже сложнее (а может и вовсе нельзя).
Задачка, однако :(
А не подскажешь как бы звучала регулярка для Proxomitron-a?
Подозреваю, что её можно было бы удачно конвертировать в юзерскрипт и обойтись без доп. софта.
Вместо location.href="..." могло быть и window.open("...").
Или <a onclick="loadURI('http://qwe.qwe/');">.
Или даже <a onclick="loadURI('%parent%/pahe.html');">
и <a onclick="loadURI(gBaseURI + '/pahe.html');">
Защититься от подобного хочется даже не столько из-за того, что подобный быдло-код мог быть написан от неумения писать нормальный, я допускаю возможность что оно может использоваться и намеренно: ведь может быть написана новая функция onclick="someFunc('...')"
которая бы делала то же самое, и такой вариант фиксить универсально уже сложнее (а может и вовсе нельзя).
Задачка, однако :(

ManHunter
(03.11.2009 в 19:39):
Только если завернуть траф через локальную проксю типа Proxomitron, и в нем уже настроить правило, чтобы регуляркой менять на лету такие ссылки на нормальные. Хотя лично я бы выбрал вариант просто не посещать подобные сайты.

Баг детектед
(03.11.2009 в 19:35):
А может ли рядовой юзер обезопасить себя (предположим, юзерскриптом) от недо-сайтов вроде topnews.ru где внизу страницы ссылки на новости №11-20 сделаны через яваскрипт, и клик по ним колесом не открывает этих новостей, а открывает всё ту же главную страницу (только в адресной строке после слэша добавляется мусор вроде #block03-6)?
Если же кликнуть ЛКМ по этой ссылке на новость, то она откроется в этом же табе/окне, но я то хочу открыть сразу несколько ссылок на разные новости в разных табах, чтобы не возвращаться каждый раз на главную.
Исходный код того куска страницы выглядит так:
[code]
<a onclick="javascript:location.href='news_id_31892.html'" href="#block03-11">
[/code]
И на самом деле больше интересует не фикс конкретно для этого сайта, конкретно для такого примера быдлокода, а более универсальное решение. Собственно вопрос: есть ли таковое?
p.s.: браузер Firefox 3.5
Если же кликнуть ЛКМ по этой ссылке на новость, то она откроется в этом же табе/окне, но я то хочу открыть сразу несколько ссылок на разные новости в разных табах, чтобы не возвращаться каждый раз на главную.
Исходный код того куска страницы выглядит так:
[code]
<a onclick="javascript:location.href='news_id_31892.html'" href="#block03-11">
[/code]
И на самом деле больше интересует не фикс конкретно для этого сайта, конкретно для такого примера быдлокода, а более универсальное решение. Собственно вопрос: есть ли таковое?
p.s.: браузер Firefox 3.5

Добавить комментарий
Заполните форму для добавления комментария
