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


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

в этом коде я использую колесо мыши для увеличения / уменьшения масштаба холста 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

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

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

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

                  | 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 для колесо мыши.
  • на панели detail обычно 2, иногда 1, но при прокрутке очень медленно ОБРАБОТЧИК СОБЫТИЙ НЕ СРАБАТЫВАЕТ ВООБЩЕ.

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

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

10 126

10 ответов:

Редактировать Сентябрь 2014

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

  • различные версии одного и того же браузера на OS X дали разные значения в прошлом, и может сделать это в будущем, и что
  • использование сенсорной панели в операционной системе 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

оригинальная попытка быть правильным следует.

вот моя первая попытка скрипта нормализовать значения. Он имеет два недостатка на 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 приветствуются.

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

вот моя сумасшедшая попытка создать кроссбраузерную когерентную и нормализованную дельту (-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

наши друзья в 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

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

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

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     |                |                |                |           |             |           |

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

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

работы здесь: 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(); }
}

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

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

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

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

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

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

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

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

в настоящее время найдено три решения:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

простое и рабочее решение:

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;
}