Нормализация скорости вращения мыши в браузерах - PullRequest
132 голосов
/ 03 апреля 2011

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

В этом коде я использую колесо мыши, чтобы увеличивать / уменьшать размер HTML5 Canvas. Я нашел код, который нормализует разницу в скорости между 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, но при очень медленной прокрутке НИКАКИХ ВЫБОРОВ СОБЫТИЙ СОБЫТИЙ ВСЕГО .

Так что вопрос:

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

Ответы [ 10 ]

50 голосов
/ 04 апреля 2011

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

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

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

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

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.

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

27 голосов
/ 30 ноября 2012

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

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, FF16, Opera 12 (OS X) и IE 7 на XP

20 голосов
/ 09 мая 2015

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

10 голосов
/ 06 июля 2014

Я создал таблицу с разными значениями, возвращаемыми различными событиями / браузерами, с учетом события 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     |                |                |                |           |             |           |
6 голосов
/ 31 июля 2012

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

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

Работа доступна здесь: 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(); }
}
3 голосов
/ 18 октября 2012

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

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

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

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

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

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

2 голосов
/ 03 июня 2011

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

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

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

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

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

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

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

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

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

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

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

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

1 голос
/ 30 июля 2011

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

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

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

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

0 голосов
/ 04 марта 2018

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

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;
}
0 голосов
/ 31 июля 2013
var onMouseWheel = function(e) {
    e = e.originalEvent;
    var delta = e.wheelDelta>0||e.detail<0?1:-1;
    alert(delta);
}
$("body").bind("mousewheel DOMMouseScroll", onMouseWheel);
...