Нормализация скорости колесика мыши в браузерах

Для другого вопроса я написал этот ответ, включая этот примерный код.

В этом коде я использую колесико мыши для увеличения/увеличения холста HTML5. Я нашел код, который нормализует разницу в скорости между Chrome и Firefox. Тем не менее, управление масштабированием в Safari намного, намного быстрее, чем в любом из них.

Вот код, который у меня есть:

var handleScroll = function(e){
  var delta = e.wheelDelta ? e.wheelDelta/40 : e.detail ? -e.detail/3 : 0;
  if (delta) ...
  return e.preventDefault() && false;
};
canvas.addEventListener('DOMMouseScroll',handleScroll,false); // For Firefox
canvas.addEventListener('mousewheel',handleScroll,false);     // Everyone else

Какой код можно использовать для получения того же значения "delta" для того же количества колесиков мыши, проходящего через Chrome v10/11, Firefox v4, Safari v5, Opera v11 и IE9?

Этот вопрос связан, но не имеет хорошего ответа.

Изменить. Дальнейшее исследование показывает, что одно событие прокрутки "вверх":

                  | evt.wheelDelta | evt.detail
------------------+----------------+------------
  Safari v5/Win7  |       120      |      0
  Safari v5/OS X  |       120      |      0
  Safari v7/OS X  |        12      |      0
 Chrome v11/Win7  |       120      |      0
 Chrome v37/Win7  |       120      |      0
 Chrome v11/OS X  |         3 (!)  |      0      (possibly wrong)
 Chrome v37/OS X  |       120      |      0
        IE9/Win7  |       120      |  undefined
  Opera v11/OS X  |        40      |     -1
  Opera v24/OS X  |       120      |      0
  Opera v11/Win7  |       120      |     -3
 Firefox v4/Win7  |    undefined   |     -3
 Firefox v4/OS X  |    undefined   |     -1
Firefox v30/OS X  |    undefined   |     -1

Кроме того, использование трекпада MacBook в OS X дает разные результаты даже при медленном движении:

  • В Safari и Chrome значение wheelDelta составляет 3 вместо 120 для колесика мыши.
  • В Firefox detail обычно 2, иногда 1, но при очень медленной прокрутке NO HANDLER FIRES FIRES AT ALL.

Итак, вопрос:

Каков наилучший способ отличить это поведение (в идеале без какого-либо пользовательского агента или обнюхивания операционной системы)?

Ответ 1

Изменить сентябрь 2014 года

Учитывая, что:

  • Различные версии одного и того же браузера в OS X дали разные значения в прошлом и могут сделать это в будущем и что
  • Использование трекпада в OS X дает очень похожие эффекты при использовании колеса мыши, но при этом дает очень разные значения событий, и все же разность устройств не может быть обнаружена JS

... Я могу только рекомендовать использовать этот простой, подсчетный код:

var handleScroll = function(evt){
  if (!evt) evt = event;
  var direction = (evt.detail<0 || evt.wheelDelta>0) ? 1 : -1;
  // Use the value as you will
};
someEl.addEventListener('DOMMouseScroll',handleScroll,false); // for Firefox
someEl.addEventListener('mousewheel',    handleScroll,false); // for everyone else

Исходная попытка быть правильной.

Вот моя первая попытка в script нормализовать значения. Он имеет два недостатка в OS X: Firefox в OS X будет давать значения 1/3, какими они должны быть, а Chrome OS X будет давать значения 1/40, что они должны быть.

// Returns +1 for a single wheel roll 'up', -1 for a single roll 'down'
var wheelDistance = function(evt){
  if (!evt) evt = event;
  var w=evt.wheelDelta, d=evt.detail;
  if (d){
    if (w) return w/d/40*d>0?1:-1; // Opera
    else return -d/3;              // Firefox;         TODO: do not /3 for OS X
  } else return w/120;             // IE/Safari/Chrome TODO: /3 for Chrome OS X
};

Вы можете проверить этот код в своем собственном браузере здесь: http://phrogz.net/JS/wheeldelta.html

Приветствуются предложения по обнаружению и улучшению поведения в Firefox и Chrome на OS X.

Изменить. Одно из предложений @Tom состоит в том, чтобы просто подсчитать каждый вызов события как один шаг, используя знак расстояния, чтобы настроить его. Это не даст больших результатов при плавной/ускоренной прокрутке на OS X, а также отлично справится с ситуациями, когда колесо мыши перемещается очень быстро (например, wheelDelta равно 240), но это происходит нечасто. Этот код теперь является рекомендуемым методом, показанным в верхней части этого ответа, по описанным там причинам.

Ответ 2

Вот моя сумасшедшая попытка создать кросс-браузерную когерентную и нормализованную дельта (-1 <= delta <= 1):

var o = e.originalEvent,
    d = o.detail, w = o.wheelDelta,
    n = 225, n1 = n-1;

// Normalize delta
d = d ? w && (f = w/d) ? d/f : -d/1.35 : w/120;
// Quadratic scale if |d| > 1
d = d < 1 ? d < -1 ? (-Math.pow(d, 2) - n1) / n : d : (Math.pow(d, 2) + n1) / n;
// Delta *should* not be greater than 2...
e.delta = Math.min(Math.max(d / 2, -1), 1);

Это полностью эмпирически, но работает неплохо на Safari 6, FF 16, Opera 12 (OS X) и IE 7 на XP

Ответ 3

Наши друзья в Facebook собрали отличное решение этой проблемы.

Я тестировал таблицу данных, которую я создаю с помощью React, и она прокручивается, как масло!

Это решение работает с различными браузерами, на Windows/Mac и с использованием трекпада/мыши.

// Reasonable defaults
var PIXEL_STEP  = 10;
var LINE_HEIGHT = 40;
var PAGE_HEIGHT = 800;

function normalizeWheel(/*object*/ event) /*object*/ {
  var sX = 0, sY = 0,       // spinX, spinY
      pX = 0, pY = 0;       // pixelX, pixelY

  // Legacy
  if ('detail'      in event) { sY = event.detail; }
  if ('wheelDelta'  in event) { sY = -event.wheelDelta / 120; }
  if ('wheelDeltaY' in event) { sY = -event.wheelDeltaY / 120; }
  if ('wheelDeltaX' in event) { sX = -event.wheelDeltaX / 120; }

  // side scrolling on FF with DOMMouseScroll
  if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
    sX = sY;
    sY = 0;
  }

  pX = sX * PIXEL_STEP;
  pY = sY * PIXEL_STEP;

  if ('deltaY' in event) { pY = event.deltaY; }
  if ('deltaX' in event) { pX = event.deltaX; }

  if ((pX || pY) && event.deltaMode) {
    if (event.deltaMode == 1) {          // delta in LINE units
      pX *= LINE_HEIGHT;
      pY *= LINE_HEIGHT;
    } else {                             // delta in PAGE units
      pX *= PAGE_HEIGHT;
      pY *= PAGE_HEIGHT;
    }
  }

  // Fall-back if spin cannot be determined
  if (pX && !sX) { sX = (pX < 1) ? -1 : 1; }
  if (pY && !sY) { sY = (pY < 1) ? -1 : 1; }

  return { spinX  : sX,
           spinY  : sY,
           pixelX : pX,
           pixelY : pY };
}

Исходный код можно найти здесь: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js

Ответ 4

Я сделал таблицу с разными значениями, возвращаемыми различными событиями/браузерами, с учетом события DOM3 wheel, которое некоторые браузеры уже поддерживают (таблица ниже).

Исходя из этого, я сделал эту функцию для нормализации скорости:

http://jsfiddle.net/mfe8J/1/

function normalizeWheelSpeed(event) {
    var normalized;
    if (event.wheelDelta) {
        normalized = (event.wheelDelta % 120 - 0) == -0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
    } else {
        var rawAmmount = event.deltaY ? event.deltaY : event.detail;
        normalized = -(rawAmmount % 3 ? rawAmmount * 10 : rawAmmount / 3);
    }
    return normalized;
}

Таблица для событий mousewheel, wheel и DOMMouseScroll:

| mousewheel        | Chrome (win) | Chrome (mac) | Firefox (win) | Firefox (mac) | Safari 7 (mac) | Opera 22 (mac) | Opera 22 (win) | IE11      | IE 9 & 10   | IE 7 & 8  |
|-------------------|--------------|--------------|---------------|---------------|----------------|----------------|----------------|-----------|-------------|-----------|
| event.detail      | 0            | 0            | -             | -             | 0              | 0              | 0              | 0         | 0           | undefined |
| event.wheelDelta  | 120          | 120          | -             | -             | 12             | 120            | 120            | 120       | 120         | 120       |
| event.wheelDeltaY | 120          | 120          | -             | -             | 12             | 120            | 120            | undefined | undefined   | undefined |
| event.wheelDeltaX | 0            | 0            | -             | -             | 0              | 0              | 0              | undefined | undefined   | undefined |
| event.delta       | undefined    | undefined    | -             | -             | undefined      | undefined      | undefined      | undefined | undefined   | undefined |
| event.deltaY      | -100         | -4           | -             | -             | undefined      | -4             | -100           | undefined | undefined   | undefined |
| event.deltaX      | 0            | 0            | -             | -             | undefined      | 0              | 0              | undefined | undefined   | undefined |
|                   |              |              |               |               |                |                |                |           |             |           |
| wheel             | Chrome (win) | Chrome (mac) | Firefox (win) | Firefox (mac) | Safari 7 (mac) | Opera 22 (mac) | Opera 22 (win) | IE11      | IE 10 & 9   | IE 7 & 8  |
| event.detail      | 0            | 0            | 0             | 0             | -              | 0              | 0              | 0         | 0           | -         |
| event.wheelDelta  | 120          | 120          | undefined     | undefined     | -              | 120            | 120            | undefined | undefined   | -         |
| event.wheelDeltaY | 120          | 120          | undefined     | undefined     | -              | 120            | 120            | undefined | undefined   | -         |
| event.wheelDeltaX | 0            | 0            | undefined     | undefined     | -              | 0              | 0              | undefined | undefined   | -         |
| event.delta       | undefined    | undefined    | undefined     | undefined     | -              | undefined      | undefined      | undefined | undefined   | -         |
| event.deltaY      | -100         | -4           | -3            | -0,1          | -              | -4             | -100           | -99,56    | -68,4 | -53 | -         |
| event.deltaX      | 0            | 0            | 0             | 0             | -              | 0              | 0              | 0         | 0           | -         |
|                   |              |              |               |               |                |                |                |           |             |           |
|                   |              |              |               |               |                |                |                |           |             |           |
| DOMMouseScroll    |              |              | Firefox (win) | Firefox (mac) |                |                |                |           |             |           |
| event.detail      |              |              | -3            | -1            |                |                |                |           |             |           |
| event.wheelDelta  |              |              | undefined     | undefined     |                |                |                |           |             |           |
| event.wheelDeltaY |              |              | undefined     | undefined     |                |                |                |           |             |           |
| event.wheelDeltaX |              |              | undefined     | undefined     |                |                |                |           |             |           |
| event.delta       |              |              | undefined     | undefined     |                |                |                |           |             |           |
| event.deltaY      |              |              | undefined     | undefined     |                |                |                |           |             |           |
| event.deltaX      |              |              | undefined     | undefined     |                |                |                |           |             |           |

Ответ 5

Еще одно или более автономное решение...

Однако это не занимает время между событиями. Некоторые браузеры, похоже, всегда запускают события с одинаковой дельтами и просто стреляют быстрее при прокрутке. Другие меняют дельт. Можно представить себе адаптивный нормализатор, который учитывает время, но это было бы в некоторой степени задействовано и неудобно использовать.

Работа доступна здесь: jsbin/iqafek/2

var normalizeWheelDelta = function() {
  // Keep a distribution of observed values, and scale by the
  // 33rd percentile.
  var distribution = [], done = null, scale = 30;
  return function(n) {
    // Zeroes don't count.
    if (n == 0) return n;
    // After 500 samples, we stop sampling and keep current factor.
    if (done != null) return n * done;
    var abs = Math.abs(n);
    // Insert value (sorted in ascending order).
    outer: do { // Just used for break goto
      for (var i = 0; i < distribution.length; ++i) {
        if (abs <= distribution[i]) {
          distribution.splice(i, 0, abs);
          break outer;
        }
      }
      distribution.push(abs);
    } while (false);
    // Factor is scale divided by 33rd percentile.
    var factor = scale / distribution[Math.floor(distribution.length / 3)];
    if (distribution.length == 500) done = factor;
    return n * factor;
  };
}();

// Usual boilerplate scroll-wheel incompatibility plaster.

var div = document.getElementById("thing");
div.addEventListener("DOMMouseScroll", grabScroll, false);
div.addEventListener("mousewheel", grabScroll, false);

function grabScroll(e) {
  var dx = -(e.wheelDeltaX || 0), dy = -(e.wheelDeltaY || e.wheelDelta || 0);
  if (e.detail != null) {
    if (e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
    else if (e.axis == e.VERTICAL_AXIS) dy = e.detail;
  }
  if (dx) {
    var ndx = Math.round(normalizeWheelDelta(dx));
    if (!ndx) ndx = dx > 0 ? 1 : -1;
    div.scrollLeft += ndx;
  }
  if (dy) {
    var ndy = Math.round(normalizeWheelDelta(dy));
    if (!ndy) ndy = dy > 0 ? 1 : -1;
    div.scrollTop += ndy;
  }
  if (dx || dy) { e.preventDefault(); e.stopPropagation(); }
}

Ответ 6

Для поддержки увеличения на сенсорных устройствах зарегистрируйтесь для событий gesturestart, gesturechange и gestureend и используйте свойство event.scale. Вы можете увидеть пример кода для этого.

Для Firefox 17 событие onwheel планируется поддерживать на настольных и мобильных версиях (согласно MDN docs на onwheel). Также для Firefox может быть полезно конкретное событие MozMousePixelScroll для Gecko (хотя, предположительно, это теперь устарело, поскольку событие DOMMouseWheel теперь устарело в Firefox).

Для Windows сам драйвер, похоже, генерирует события WM_MOUSEWHEEL, WM_MOUSEHWHEEL (и, возможно, событие WM_GESTURE для панорамирования тачпада?). Это объясняет, почему Windows или браузер не нормализуют сами значения событий mousewheel (и могут означать, что вы не можете написать надежный код для нормализации значений).

Для onwheel (не onmousewheel) события поддержка в Internet Explorer для IE9 и IE10, вы также можете использовать стандарт W3C onwheel событие. Однако одна метка может быть значением, отличным от 120 (например, одна метка становится 111 (вместо -120) на моей мыши используя эту тестовую страницу). Я написал другую статью с другими подробными событиями колес, которые могут быть релевантными.

В основном в моем собственном тестировании для событий с колесами (я пытаюсь нормализовать значения для прокрутки), я обнаружил, что получаю различные значения для ОС, поставщика браузера, версии браузера, типа события и устройства (мышь Microsoft tiltwheel, жесты тачпада ноутбука, тачпад для ноутбука с scrollzone, волшебная мышь Apple, яркий мышиный прокрутка мыши, тачпад Mac и т.д. и т.д.).

И нужно игнорировать множество побочных эффектов из конфигурации браузера (например, Firefox mousewheel.enable_pixel_scrolling, chrome -scroll-pixels = 150), настройки драйвера (например, сенсорная панель Synaptics) и конфигурация ОС (настройки мыши Windows, OSX Настройки мыши, настройки кнопки X.org).

Ответ 7

Это проблема, с которой я боролся в течение нескольких часов сегодня, а не в первый раз: (

Я пытаюсь суммировать значения над "swipe" и видеть, как разные браузеры сообщают о значениях, и они сильно различаются, причем Safari сообщает о величине больших чисел почти на всех платформах. Отчеты Chrome довольно много (например, В 3 раза больше), чем firefox, firefox сбалансирован в долгосрочной перспективе, но совсем другой среди платформ на небольших движениях (на Ubuntu gnome, почти только +3 или -3, кажется, что он суммирует небольшие события, а затем посылает большой "+3" )

Текущие решения, найденные сейчас, три:

  • Уже упоминавшийся "использовать только знак", который убивает любое ускорение
  • Снимите браузер до младшей версии и платформы и правильно настройте
  • Недавно Qooxdoo реализовал алгоритм самоадаптации, который в основном пытается масштабировать дельта на основе минимального и максимального значений, полученных до сих пор.

Идея в Qooxdoo хороша и работает, и это единственное решение, которое в настоящее время я нашел полностью совместимым кросс-браузер.

К сожалению, он стремится перенормировать также ускорение. Если вы попробуете (в своих демонстрациях) и прокрутите вверх и вниз с максимальной скоростью на некоторое время, вы заметите, что прокрутка очень быстро или очень медленно в основном производит почти такое же количество движения. Наоборот, если вы перезагрузите страницу и проведите очень медленно, вы заметите, что она будет прокручиваться довольно быстро ".

Это неприятно для пользователя Mac (как и я), используемого для мощных прокруток прокрутки на тачпаде и ожидающих попасть в верхнюю или нижнюю часть прокрученной вещи.

Более того, поскольку он уменьшает скорость мыши на основе максимального полученного значения, чем больше ваш пользователь пытается его ускорить, тем больше он будет замедляться, а пользователь "медленной прокрутки" будет испытывать довольно быстрые скорости.

Это делает это (иначе блестящее) решение немного лучшей реализацией решения 1.

Я портировал решение для плагина jQuery mousewheel: http://jsfiddle.net/SimoneGianni/pXzVv/

Если вы некоторое время играете с ним, вы увидите, что начнете получать довольно однородные результаты, но вы также заметите, что он имеет тенденцию к + 1/-1 значениям довольно быстро.

Теперь я работаю над улучшением его, чтобы лучше определять пики, чтобы они не посылали все "вне шкалы". Также было бы неплохо также получить значение поплавка от 0 до 1 в качестве значения дельта, так что будет когерентный выход.

Ответ 8

Существует определенно нет простого способа нормализации для всех пользователей во всех ОС во всех браузерах.

Он становится хуже, чем перечисленные вами варианты - на моей установке WindowsXP + Firefox3.6 моя колесико мыши делает 6 на одно прокрутку - возможно, потому что где-то я забыл, что ускорил колесо мыши, либо в ОС, либо где-то примерно: config

Однако я работаю над подобной проблемой (с аналогичным приложением btw, но не-canvas), и это происходит со мной, просто используя знак треугольника +1/-1 и измеряя во времени последний раз, когда он запускал, у вас будет скорость ускорения, т.е. если кто-то прокручивает один раз несколько раз за несколько секунд (что я бы поставил на то, как это делают карты Google).

Концепция, похоже, хорошо работает в моих тестах, просто сделайте что-то меньшее, чем 100 мс, добавьте ускорение.

Ответ 9

var onMouseWheel = function(e) {
    e = e.originalEvent;
    var delta = e.wheelDelta>0||e.detail<0?1:-1;
    alert(delta);
}
$("body").bind("mousewheel DOMMouseScroll", onMouseWheel);

Ответ 10

Простое и эффективное решение:

private normalizeDelta(wheelEvent: WheelEvent):number {
    var delta = 0;
    var wheelDelta = wheelEvent.wheelDelta;
    var deltaY = wheelEvent.deltaY;
    // CHROME WIN/MAC | SAFARI 7 MAC | OPERA WIN/MAC | EDGE
    if (wheelDelta) {
        delta = -wheelDelta / 120; 
    }
    // FIREFOX WIN / MAC | IE
    if(deltaY) {
        deltaY > 0 ? delta = 1 : delta = -1;
    }
    return delta;
}