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


147

На інше запитання я склав цю відповідь , включаючи цей зразок коду .

У цьому коді я використовую колесо миші для збільшення / зменшення полотна 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 (можливо, неправильно)
 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 в ОС X дає різні результати навіть при повільному переміщенні:

  • Для Safari та Chrome wheelDeltaце значення 3, а не 120 для колеса миші.
  • На Firefox detailце, як правило 2, іноді 1, але при прокручуванні дуже повільно НЕ БУДЬ ВІДПОВІДАЛЬНИЙ ВІДПОВІДНИК .

Тож питання:

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


Вибачте, я видалив своє запитання. Я зараз пишу відповідь. Перш ніж я зайти набагато далі, ти говориш про прокручування Safari на Mac OS X? Коли ви трохи прокручуєте, вона трохи прокручується, але якщо ви зберігаєте постійну швидкість, вона прогресивно стає швидшою?
Блендер

@Blender Я зараз тестую ОС OS X, і так, Safari - це вищий розмір, який збільшується приблизно в 20 разів швидше, ніж у Chrome. На жаль, у мене не прикріплена фізична миша, тому моє тестування обмежено двома пальцями ≈еквівалентних відстаней та швидкостей.
Фрогз

Я оновив питання з деталями щодо поведінки топ-5 веб-переглядачів у OS X та Win7. Це мінне поле, проблема Chrome на OS X виявляється проблематичною.
Фрогз

@Phrogz Чи не так e.wheelDelta/120?
Šime Vidas

@ ŠimeVidas Так, я скопіював і використовував код явно був неправильним. Кращий код ви можете побачити в моїй відповіді нижче .
Фрогз

Відповіді:


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

Оригінальна спроба бути правильною наступна.

Ось моя перша спроба сценарію нормалізувати значення. У ОС 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), але це трапляється нечасто. Цей код тепер є рекомендованою методикою, показаною у верхній частині цієї відповіді, з причин, описаних там.


@ ŠimeVidas Спасибі, це в основному те, що у мене є, за винятком того, що я також враховую 1/3 різниці в Opera OS X.
Phrogz

@Phrogz, чи є у вас оновлена ​​версія вересня 2014 року з усіма доданими ОС X / 3? Це було б чудовим доповненням для громади!
Бась

@Phrogz, це було б чудово. Тут у мене немає Mac для тестування ... (Я був би радий віддати за це щедрість, навіть якщо я сам не маю великої репутації;))
Basj

1
У вікнах Firefox 35.0.1, wheelDelta не визначено, а деталізація завжди 0, що призводить до відмови наданого коду.
Макс Стратер

1
@MaxStrater Зіткнувшись з тією ж проблемою, я додав "deltaY", щоб подолати це в напрямку, як це (((evt.deltaY <0 || evt.wheelDelta>0) || evt.deltaY < 0) ? 1 : -1)не впевнений, що з цим дізнається QA.
Брок

28

Ось моя шалена спроба створити крос-браузерну когерентну та нормалізовану дельту (-1 <= дельта <= 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
Якби я міг подати ще 10 разів, я міг би. Дуже дякую!
justnorris

Чи можете ви мати повний функціональний код у демонстраційній версії (наприклад, jsFiddle)?
adardesign

Чи є причина , щоб кешувати в event-Об'єкт в o?
yckart

Ніякого немає. Ця oзмінна існує, щоб показати, що ми хочемо, щоб оригінал події, а не завершена подія, така як jQuery або інші бібліотеки, може передаватись обробникам подій.
смерть

@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
Більш пряме посилання, яке не було в комплекті з оригінальним кодом для нормалізаціїWHeel.js github.com/facebook/fixed-data-table/blob/master/src/…
Робін Луйтен

Дякуємо @RobinLuiten, оновивши оригінальну публікацію.
Джордж

Цей матеріал геніальний. Щойно скористався ним і працює як шарм! Гарна робота Facebook :)
perry

Чи можете ви навести приклад того, як ним користуватися? Я спробував це, і він працює у FF, але не в Chrome або IE (11) ..? Спасибі
Андрій

2
Для тих, хто використовує npm, є готовий до використання пакет лише цього коду, який уже видобутий із таблиці Fixed Data 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     |                |                |                |           |             |           |

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

1
Це рішення зовсім не працює з Chrome на Mac із Trackpad.
justnorris

@Norris Я вважаю, що це зараз. Щойно знайшли це запитання, і приклад тут працює на моїй книзі з хромом
Гаррі Морено

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

3

Для підтримки масштабування на сенсорних пристроях зареєструйтесь на події gesturestart, зміну жестів та gestureend та скористайтеся властивістю event.scale. Ви можете побачити приклад коду для цього.

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

Для Windows, здається, сам драйвер генерує події WM_MOUSEWHEEL, WM_MOUSEHWHEEL (а може бути подія WM_GESTURE для панорамування сенсорної панелі?). Це пояснило б, чому Windows чи браузер, здається, не нормалізують самі події колісного колеса (а це може означати, що ви не можете написати надійний код для нормалізації значень).

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

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

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


2

Це проблема, з якою я боровся вже кілька годин сьогодні, і не вперше :(

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

Зараз знайдено три рішення:

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

Ідея в Qooxdoo хороша і працює, і є єдиним рішенням, яке я на даний момент виявив повністю сумісним крос-браузером.

На жаль, воно, як правило, переносить норму прискорення. Якщо ви спробуєте це (на їх демонстраціях) і прокручуєте вгору і вниз на максимальній швидкості деякий час, ви помітите, що прокрутка надзвичайно швидка або надзвичайно повільна в основному створює майже стільки ж руху. З іншого боку, якщо ви перезавантажите сторінку та проведіть пальцем лише дуже повільно, ви помітите, що вона прокручується досить швидко ".

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

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

Це робить це (інакше блискучим) рішенням дещо кращою реалізацією рішення 1.

Я переніс рішення на плагін jwery mousewheel: http://jsfiddle.net/SimoneGianni/pXzVv/

Якщо ви будете грати з ним деякий час, ви побачите, що ви почнете отримувати досить однорідні результати, але ви також помітите, що воно має тенденцію до значень +1 / -1 досить швидко.

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


1

Однозначно не існує простого способу нормалізації для всіх користувачів у всіх ОС у всіх браузерах.

Це стає гірше, ніж перелічені вами варіанти - у моїй установці WindowsXP + Firefox3.6 мій колесо миші робить 6 за один проміжок прокрутки - можливо, тому, що десь я забув, я прискорив колесо миші, або в ОС, або десь приблизно: конфігурація

Однак я працюю над подібною проблемою (із аналогічною програмою btw, але не на полотні), і мені це трапляється, просто використовуючи знак дельти +1 / -1 і вимірюючи з часом останній раз, коли він вистрілив, ви мають швидкість прискорення, тобто. якщо хтось прокручує один раз проти декількох разів за кілька моментів (на що я б сказав, що це робить Google Maps).

Ця концепція, здається, добре працює в моїх тестах, просто зробіть що-небудь менше 100мс, що додасть прискорення.


-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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.