Blog. Just Blog

Обработка колесика мыши на JavaScript

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Web-мастеру и не только | Автор: ManHunter
Сейчас трудно представить мышь без хотя бы одного колесика. Оно используется для прокрутки страницы, для изменения масштаба каких-либо элементов, листания списков и других действий. В основном полная обработка колесика выполняется только в стационарных приложениях, а на сайтах дело ограничивается штатными средствами браузеров. Мы не будем отставать от прогресса, и сегодня разберем как грамотно обрабатывать колесико мыши в современных web-приложениях. Начинаем с теории.


Обработчик события - функция на JavaScript, которая назначается некоторой паре "объект страницы" + "название события". Когда для объекта происходит назначенное событие, выполняются все обработчики, назначенные этому объекту.


Вращение колесика мышки - это тоже событие, значит его тоже можно обработать. Но тут есть особенность: в разных браузерах событие вращения колесика обозначается по-разному. Причину этого я объяснить не могу, впрочем как и многие другие танцы с бубнами для достижения полноценной кроссбраузерности. Сперва напишем функции установки и отмены события для любого элемента страницы, причем сделаем их универсальными, они нам еще не раз пригодятся в будущем. В браузере Internet Explorer обработчики добавляются функцией attachEvent, а названия событий имеют вид 'on'+событие, например, 'onclick', 'onload', 'onmousewheel' и т.д. В браузерах на движке Gecko (Mozilla, Firefox), на движке WebKit (Chrome, Safari) и Opera обработчики добавляются функцией addEventListener, а названия событий никак не меняются, то есть 'scroll', 'keypress', 'mousewheel'. И отдельно в этом списке стоит имя события от колесика мышки для браузеров на движке Gecko, оно единственное и уникальное - 'DOMMouseScroll'. Теперь попробуем все эти данные собрать в кучу.

Получилась вот такая универсальная кроссбраузерная функция установки обработчика события для любого элемента на странице:
  1. //------------------------------------------------------------------
  2. // Функция установки обработчика события
  3. // Параметры вызова:
  4. //   hElem     - DOM-элемент или его ID
  5. //   eventName - событие
  6. //   callback  - функция, которая будет вызвана при событии
  7. // На выходе:
  8. //   TRUE  - обработчик установлен
  9. //   FALSE - элемент не найден или браузер не поддерживает события
  10. //------------------------------------------------------------------
  11. function hookEvent(hElemeventNamecallback) {
  12.   if (typeof(hElem) == 'string') {
  13.     // Если передан ID, то получить DOM-элемент
  14.     hElem document.getElementById(hElem);
  15.   }
  16.   // Если такого элемента нет, то возврат с ошибкой
  17.   if (!hElem) { return false; }
  18.  
  19.   if (hElem.addEventListener) {
  20.     if (eventName == 'mousewheel') {
  21.       // Событие вращения колесика для Mozilla
  22.       hElem.addEventListener('DOMMouseScroll'callbackfalse);
  23.     }
  24.     // Колесико для Opera, WebKit-based, а также любые другие события
  25.     // для всех браузеров кроме Internet Explorer
  26.     hElem.addEventListener(eventNamecallbackfalse);
  27.   }
  28.   else if (hElem.attachEvent) {
  29.     // Все события для Internet Explorer
  30.     hElem.attachEvent('on' eventNamecallback);
  31.   }
  32.   else { return false; }
  33.   return true;
  34. }
Параметр hElem - элемент страницы или его идентификатор, в этом случае функция попробует найти этот элемент самостоятельно. eventName - имя события 'click', 'mousewheel' и т.д. callback - имя функции обработчика, которая получит управление при возникновении события. Если на один элемент установлено несколько разных обработчиков одинакового события, то порядок их срабатывания зависит только от особенностей того или иного браузера, никаких стандартов, к сожалению, тут нет.

Функция снятия обработчика события по функционалу практически полностью повторяет функцию установки, за исключением того, что в ней используются функции detachEvent и removeEventListener. Каждое событие отменяется индивидуально.
  1. //------------------------------------------------------------------
  2. // Функция снятия обработчика события
  3. // Параметры вызова:
  4. //   hElem     - DOM-элемент или его ID
  5. //   eventName - событие
  6. //   callback  - функция обработки события, которую надо отменить
  7. //------------------------------------------------------------------
  8. function unhookEvent(hElemeventNamecallback) {
  9.   if (typeof(hElem) == 'string') {
  10.     // Если передан ID, то получить DOM-элемент
  11.     hElem document.getElementById(hElem);
  12.   }
  13.   // Если такого элемента нет, то возврат с ошибкой
  14.   if (!hElem) { return false; }
  15.  
  16.   if (hElem.removeEventListener) {
  17.     if (eventName == 'mousewheel') {
  18.       // Событие вращения колесика для Mozilla
  19.       hElem.removeEventListener('DOMMouseScroll'callbackfalse);
  20.     }
  21.     // Колесико для Opera, WebKit-based, а также любые другие события
  22.     // для всех браузеров кроме Internet Explorer
  23.     hElem.removeEventListener(eventNamecallbackfalse);
  24.   }
  25.   else if (hElem.detachEvent) {
  26.     // Все события для Internet Explorer
  27.     hElem.detachEvent('on' eventNamecallback);
  28.   }
  29.   else { return false; }
  30.   return true;
  31. }
Параметры вызова совпадают с параметрами вызова функции установки обработчика. Теперь перейдем к самой функции обработчика. В ней надо получить два параметра: направление вращения колесика и элемент страницы, от которого пришло это событие. Во всех браузерах, кроме Internet Explorer и некоторых версий Opera, функции обработчика в качестве параметра передается объект события, в Internet Explorer и Opera событие записывается в window.event. Теперь, когда мы получили объект события, из него можно извлечь элемент и значение поворота колесика. В браузерах Internet Explorer и Opera элемент содержится в свойстве события srcElement, в остальных браузерах в свойстве target. Тут все просто. А вот со значением поворота колесика сложнее. Само значение поворота колесика в браузерах Internet Explorer и Opera элемент содержится в свойстве события wheelDelta, в остальных браузерах в свойстве detail. Но числовое значение существенно разнится практически во всех браузерах. Проведем эксперимент: установим обработчик колесика мышки, откроем страничку в разных браузерах, повернем колесико в какое-нибудь одинаковое направление и выведем на экран числовое значение поворота. Результаты эксперимента (колесико вращалось вверх):

Internet Explorer (wheelDelta) : 120
Firefox (detail) : -3
Google Chrome (wheelDelta): 120
Старые версии Chrome (wheelDelta): 12000
Safari (wheelDelta): 120
Opera (detail) : -3 / (wheelDelta): 120

Опять же для меня остается загадкой логика и несогласованность разработчиков различных браузеров. Придется учитывать эти особенности в нашей функции обработчика. И остается последний момент. Все функции событий обрабатываются последовательно, они как бы "всплывают". Это значит, что после выполнения нашего обработчика колесика мыши управление получат следующие или же обработчики по умолчанию, то есть страница или фрейм прокрутятся как обычно. Чтобы подавить подобные действия браузера по умолчанию, надо сделать так, чтобы наш обработчик события стал завершающим в цепочке. Для этого напишем вспомогательную функцию, которая будет вызываться в самом конце нашего обработчика, и будет подавлять или отменять все последующие обработчики. Входной параметр для нее - объект события. Функция универсальная, подходит для любых событий, в ней учтены особенности всех браузеров.
  1. //------------------------------------------------------------------
  2. // Кроссбраузерная функция подавления события
  3. //------------------------------------------------------------------
  4. function cancelEvent(e) {
  5.   e window.event;
  6.   if (e.stopPropagation) {
  7.     e.stopPropagation();
  8.   }
  9.   if (e.preventDefault) {
  10.     e.preventDefault();
  11.   }
  12.   e.cancelBubble true;
  13.   e.cancel true;
  14.   e.returnValue false;
  15.   return false;
  16. }
Теперь у нас есть все данные для написания полноценного обработчика колесика мыши. Вот примерная заготовка:
  1. //------------------------------------------------------------------
  2. // Пример функции обработки колесика мыши
  3. //------------------------------------------------------------------
  4. function MouseWheelFunction(e) {
  5.   e window.event;
  6.   // Получить элемент, на котором произошло событие
  7.   var wheelElem e.target e.target e.srcElement;
  8.   // Получить значение поворота колесика мыши
  9.   var wheelData e.detail e.detail * -e.wheelDelta 40;
  10.   // В движке WebKit возвращается значение в 100 раз больше
  11.   if (Math.abs(wheelData)>100) { wheelData=Math.round(wheelData/100); }
  12.  
  13.   // Теперь в переменной wheelElem содержится элемент, который получил
  14.   // собщение от колесика мыши, а в wheelData значение поворота колеса
  15.  
  16.   ...
  17.   Ваш обработчик колесика мыши
  18.   ...
  19.  
  20.   // Отменить штатные действия браузера при кручении колеса мыши
  21.   return cancelEvent(e);
  22. }
Переменная wheelData содержит числовое значение поворота колесика мыши: отрицательное значение - колесико повернуто на себя, положительное значение - поворот от себя. Переменная wheelElem - элемент web-страницы, от которого пришло событие. В приложении пример скрипта обработки колесика мыши и html-страница с различными элементами, реагирующими на колесико: картинка, таблица, div и textarea.

Пример скрипта обработки колесика мыши на JavaScriptПример скрипта обработки колесика мыши на JavaScript

Mousewheel.Demo.zip (36,728 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 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: примеров выше :)
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.
Баг детектед (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('...')"
которая бы делала то же самое, и такой вариант фиксить универсально уже сложнее (а может и вовсе нельзя).

Задачка, однако :(
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

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2025
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0056 сек.) / Память: 4.5 Mb
Наверх