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

147

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

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

Какой код можно использовать, чтобы получить одинаковое значение «дельта» для одинакового количества прокрутки колеса мыши в 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 (возможно неправильно)
 Chrome v37 / OS X | 120 | 0
        IE9 / Win7 | 120 | не определено
  Opera v11 / OS X | 40 | -1
  Opera v24 / OS X | 120 | 0
  Opera v11 / Win7 | 120 | -3
 Firefox v4 / Win7 | неопределенный | -3
 Firefox v4 / OS X | неопределенный | -1
Firefox v30 / OS X | неопределенный | -1

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

  • В Safari и Chrome wheelDeltaэто значение 3 вместо 120 для колеса мыши.
  • На Firefox detailэто обычно 2, иногда 1, но при очень медленной прокрутке НИКАКОГО СОБЫТИЯ HANDLER не запускается вообще .

Итак, вопрос в следующем:

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

Phrogz
источник
Извините, я удалил свой вопрос. Я пишу ответ прямо сейчас. Прежде чем я углублюсь, вы говорите о прокрутке в Safari в Mac OS X? Когда вы немного прокручиваете, это немного прокручивается, но если вы сохраняете постоянную скорость, она постепенно становится быстрее?
Блендер
@Blender Я сейчас тестирую на OS X, и да, Safari - это выброс, который примерно в 20 раз быстрее, чем Chrome. К сожалению, у меня нет физической мыши, поэтому мое тестирование ограничено двумя пальцами с эквивалентными расстояниями и скоростями.
Phrogz
Я обновил вопрос с подробностями о поведении 5 лучших браузеров в OS X и Win7. Это минное поле, и Chrome на OS X выглядит проблематично.
Phrogz
@Phrogz Не должно ли быть e.wheelDelta/120?
Шиме Видас
@ ŠimeVidas Да, код, который я скопировал и использовал, был явно неправильным. Вы можете увидеть лучший код в моем ответе ниже .
Phrogz

Ответы:

57

Изменить сентябрь 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

Первоначальная попытка быть правильной следует.

Вот моя первая попытка сценария нормализации значений. Он имеет два недостатка в 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), но это случается нечасто. Этот код теперь является рекомендуемой техникой, показанной в верхней части этого ответа, по причинам, описанным там.

Phrogz
источник
@ ŠimeVidas Спасибо, это в основном то, что у меня есть, за исключением того, что я также учитываю разницу в 1/3 в Opera OS X.
Phrogz
@Phrogz, у вас есть обновленная версия в сентябре 2014 года со всеми добавленными OS X / 3? Это было бы отличным дополнением для сообщества!
Basj
@Phrogz, это было бы здорово. У меня нет Mac для тестирования ... (Я был бы рад получить награду за это, даже если бы у меня не было особой репутации;))
Basj
1
В Windows Firefox 35.0.1 wheelDelta не определено, а детализация всегда равна 0, что приводит к сбою в работе кода.
Макс Стратер
1
@MaxStrater Столкнувшись с той же проблемой, я добавил «deltaY», чтобы преодолеть это в подобном направлении (((evt.deltaY <0 || evt.wheelDelta>0) || evt.deltaY < 0) ? 1 : -1), хотя и не уверен, что QA обнаружит с этим, хотя.
Брок
28

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

smrtl
источник
3
Если бы я мог поднять голос еще 10 раз, я бы мог. Спасибо вам большое!
justnorris
Можете ли вы иметь полный функциональный код в демоверсии (например, jsFiddle)?
adardesign
Есть ли причина , чтобы кэшировать в event-объект в o?
ykart
Нет там нет. oПеременная есть , чтобы показать , что мы хотим оригинальное событие , а не перенесенного событие , как JQuery или других библиотек может перейти к обработчиков событий.
smrtl
@smrtl не могли бы вы объяснить n и n1? Для чего эти переменные?
Om3ga
28

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

Джордж
источник
3
Более прямая ссылка, которая не была привязана к исходному коду для normalizeWHeel.js github.com/facebook/fixed-data-table/blob/master/src/…
Робин Люитен
Спасибо @RobinLuiten, обновляя оригинальный пост.
Джордж
Это великолепно. Просто использовал это и работает как шарм! Хорошая работа Facebook :)
Перри
Не могли бы вы привести пример, как его использовать? Я пробовал, и он работает в FF, но не в Chrome или IE (11) ..? Спасибо
Андрей
2
Для всех, кто использует npm, есть готовый пакет с этим кодом, уже извлеченный из таблицы фиксированных данных Facebook. Смотрите здесь для получения более подробной информации npmjs.com/package/normalize-wheel
Саймон Уотсон
11

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

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

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     |                |                |                |           |             |           |
Sergio
источник
2
Приводит к различным скоростям прокрутки в текущих Safari и Firefox под macOS.
Ленар Хойт
6

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

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

Работать можно здесь: 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(); }
}
Marijn
источник
1
Это решение совсем не работает с Chrome на Mac с трекпадом.
justnorris
@Norris Я верю, что теперь. Только что нашел этот вопрос, и пример здесь работает на моем MacBook с Chrome
Гарри Морено
4

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

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;
}
Marek
источник
3

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

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

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

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

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

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

robocat
источник
2

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

Я пытался суммировать значения с помощью «пролистывания» и посмотреть, как разные браузеры сообщают о значениях, и они сильно различаются: 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

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

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

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

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

ck_
источник
-2
var onMouseWheel = function(e) {
    e = e.originalEvent;
    var delta = e.wheelDelta>0||e.detail<0?1:-1;
    alert(delta);
}
$("body").bind("mousewheel DOMMouseScroll", onMouseWheel);
Матье Шавиньи
источник