Normalizzazione della velocità della rotellina del mouse tra i browser


147

Per una domanda diversa ho composto questa risposta , incluso questo codice di esempio .

In quel codice uso la rotellina del mouse per ingrandire / ridurre una tela HTML5. Ho trovato del codice che normalizza le differenze di velocità tra Chrome e Firefox. Tuttavia, la gestione dello zoom in Safari è molto, molto più veloce rispetto a una di queste.

Ecco il codice che ho attualmente:

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

Quale codice posso usare per ottenere lo stesso valore 'delta' per la stessa quantità di rotellina del mouse su Chrome v10 / 11, Firefox v4, Safari v5, Opera v11 e IE9?

Questa domanda è correlata, ma non ha una buona risposta.

Modifica : ulteriori indagini mostrano che un evento di scorrimento 'su' è:

                  | 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 (possibilmente sbagliato)
 Chrome v37 / OS X | 120 | 0
        IE9 / Win7 | 120 | non definito
  Opera v11 / OS X | 40 | -1
  Opera v24 / OS X | 120 | 0
  Opera v11 / Win7 | 120 | -3
 Firefox v4 / Win7 | indefinito | -3
 Firefox v4 / OS X | indefinito | -1
Firefox v30 / OS X | indefinito | -1

Inoltre, l'utilizzo del trackpad del MacBook su OS X offre risultati diversi anche quando ci si sposta lentamente:

  • Su Safari e Chrome, il wheelDeltavalore è 3 anziché 120 per la rotellina del mouse.
  • Su Firefox , di detailsolito 2, a volte 1, ma quando si scorre molto lentamente, NESSUN EVENTO HANDLER HANDLER ALCUNA .

Quindi la domanda è:

Qual è il modo migliore per differenziare questo comportamento (idealmente senza alcun agente utente o sistema operativo sniffing)?


Scusa, ho eliminato la mia domanda. Sto scrivendo una risposta proprio ora. Prima di andare molto oltre, stai parlando dello scorrimento su Safari su Mac OS X? Quando scorri un po ', scorre un po', ma se mantieni una velocità costante, diventa progressivamente più veloce?
Blender

@Blender Sto testando su OS X in questo momento, e sì, Safari è il valore anomalo che sta ingrandendo circa 20 volte più velocemente di Chrome. Sfortunatamente non ho un mouse fisico attaccato, quindi il mio test è limitato a due dita di ≈ distanze e velocità equivalenti.
Phrogz,

Ho aggiornato la domanda con dettagli sul comportamento dei primi 5 browser su OS X e Win7. È un campo minato, con Chrome su OS X che sembra essere il problema anomalo.
Phrogz,

@Phrogz Non dovrebbe essere e.wheelDelta/120?
Šime Vidas

@ ŠimeVidas Sì, il codice che ho copiato e che stavo usando era chiaramente sbagliato. Puoi vedere un codice migliore nella mia risposta qui sotto .
Phrogz,

Risposte:


57

Modifica settembre 2014

Dato che:

  • Versioni diverse dello stesso browser su OS X hanno prodotto valori diversi in passato e potrebbero farlo anche in futuro
  • L'uso del trackpad su OS X produce effetti molto simili all'utilizzo di una rotellina del mouse, ma fornisce valori di eventi molto diversi , e tuttavia la differenza del dispositivo non può essere rilevata da JS

... posso solo consigliare di utilizzare questo semplice codice di conteggio basato sui segni:

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

Segue il tentativo originale di essere corretto.

Ecco il mio primo tentativo di uno script per normalizzare i valori. Ha due difetti su OS X: Firefox su OS X produrrà valori 1/3 come dovrebbero essere e Chrome su OS X produrrà valori 1/40 come dovrebbero essere.

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

Puoi provare questo codice sul tuo browser qui: http://phrogz.net/JS/wheeldelta.html

Suggerimenti per rilevare e migliorare il comportamento su Firefox e Chrome su OS X sono i benvenuti.

Modifica : un suggerimento di @Tom è semplicemente contare ogni chiamata di evento come una singola mossa, usando il segno della distanza per regolarla. Ciò non darà grandi risultati con lo scorrimento regolare / accelerato su OS X, né gestirà perfettamente i casi in cui la rotellina del mouse viene spostata molto velocemente (ad es. wheelDelta240), ma ciò accade raramente. Questo codice è ora la tecnica consigliata mostrata nella parte superiore di questa risposta, per i motivi ivi descritti.


@SimeVidas Grazie, questo è fondamentalmente quello che ho, tranne che conto anche della differenza 1/3 su Opera OS X.
Phrogz

@Phrogz, hai una versione aggiornata a settembre 2014 con tutti gli OS X / 3 aggiunti? Questa sarebbe una grande aggiunta per la comunità!
Basj,

@Phrogz, sarebbe fantastico. Non ho un Mac qui per testare ... (Sarei felice di dare una taglia per questo, anche se non ho molta reputazione me stesso;))
Basj

1
Su Windows Firefox 35.0.1, wheelDelta non è definito e i dettagli sono sempre 0, il che rende il codice fornito non riuscito.
Max Strater,

1
@MaxStrater Di fronte allo stesso problema, ho aggiunto "deltaY" per superarlo in una direzione del genere, ma (((evt.deltaY <0 || evt.wheelDelta>0) || evt.deltaY < 0) ? 1 : -1)non sono sicuro di ciò che il QA scopre.
Brock,

28

Ecco il mio folle tentativo di produrre un delta coerente e normalizzato per più browser (-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);

Questo è totalmente empirico ma funziona abbastanza bene su Safari 6, FF 16, Opera 12 (OS X) e IE 7 su XP


3
Se potessi votare altre 10 volte. Grazie mille!
Justnorris,

Potete per favore avere il codice funzionale completo in una demo (ad esempio jsFiddle)?
adardesign,

C'è una ragione per memorizzare nella cache il event-oggetto in o?
yckart,

No, non c'è nessuno. La ovariabile è lì per mostrare che vogliamo l'evento originale e non un evento concluso come jQuery o altre librerie può passare ai gestori di eventi.
smrtl

@smrtl potresti per favore spiegare n e n1? A cosa servono queste variabili?
Om3ga,

28

I nostri amici di Facebook hanno messo insieme un'ottima soluzione a questo problema.

Ho testato su una tabella di dati che sto costruendo usando React e scorre come burro!

Questa soluzione funziona su una varietà di browser, su Windows / Mac, ed entrambi utilizzano trackpad / mouse.

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

Il codice sorgente è disponibile qui: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js


3
Un link più diretto che non è stato raggruppato al codice originale per normalizeWHeel.js github.com/facebook/fixed-data-table/blob/master/src/…
Robin Luiten,

Grazie @RobinLuiten, aggiornamento post originale.
George

Questa roba è geniale. Appena usato e funziona come un fascino! Bel lavoro Facebook :)
perry,

Potresti dare qualche esempio su come usarlo? L'ho provato e funziona in FF ma non in Chrome o IE (11) ..? Grazie
Andrew,

2
Per chiunque usi npm c'è un pacchetto pronto all'uso di questo codice già estratto dalla tabella dei dati fissi di Facebook. Vedi qui per maggiori dettagli npmjs.com/package/normalize-wheel
Simon Watson,

11

Ho creato una tabella con valori diversi restituiti da diversi eventi / browser, tenendo conto wheel dell'evento DOM3 che alcuni browser già supportano (tabella sotto).

Sulla base di ciò ho creato questa funzione per normalizzare la velocità:

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

Tabella per mousewheel, wheele DOMMouseScrollgli eventi:

| 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
Risultati in diverse velocità di scorrimento nell'attuale Safari e Firefox in macOS.
Lenar Hoyt,

6

Un'altra soluzione più o meno autonoma ...

Tuttavia, ciò non richiede tempo tra gli eventi. Alcuni browser sembrano generare sempre eventi con lo stesso delta e attivarli più velocemente durante lo scorrimento rapido. Altri variano i delta. Si può immaginare un normalizzatore adattivo che tenga conto del tempo, ma che sarebbe in qualche modo coinvolto e scomodo da usare.

Funzionando disponibile qui: 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
Questa soluzione non funziona affatto con Chrome su Mac con Trackpad.
Justnorris,

@Norris Credo che lo faccia ora. Ho appena trovato questa domanda e l'esempio qui funziona sul mio macbook con Chrome
Harry Moreno,

4

Soluzione semplice e funzionante:

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

Per il supporto dello zoom su dispositivi touch, registrati per gli eventi gesturestart, gesturechange e gestend e usa la proprietà event.scale. Puoi vedere il codice di esempio per questo.

Per Firefox 17 onwheelè previsto che l' evento sia supportato dalle versioni desktop e mobile (secondo i documenti MDN su Onwheel ). Anche per Firefox forse l' MozMousePixelScrollevento specifico di Gecko è utile (anche se presumibilmente questo è ora deprecato poiché l'evento DOMMouseWheel è ora deprecato in Firefox).

Per Windows, il driver stesso sembra generare gli eventi WM_MOUSEWHEEL, WM_MOUSEHWHEEL (e forse l'evento WM_GESTURE per il panning del touchpad?). Ciò spiegherebbe perché Windows o il browser non sembrano normalizzare i valori degli eventi della rotellina del mouse (e potrebbe significare che non è possibile scrivere codice affidabile per normalizzare i valori).

Per il supporto di eventi onwheel( non onmousewheel) in Internet Explorer per IE9 e IE10, è anche possibile utilizzare l' evento standard W3C onwheel . Tuttavia, una tacca può essere un valore diverso da 120 (ad esempio una singola tacca diventa 111 (anziché -120) sul mio mouse usando questa pagina di prova ). Ho scritto un altro articolo con altri dettagli sugli eventi della ruota che potrebbero essere rilevanti.

Fondamentalmente nei miei test per gli eventi della ruota (sto cercando di normalizzare i valori per lo scorrimento), ho scoperto che ottengo valori variabili per sistema operativo, fornitore del browser, versione del browser, tipo di evento e dispositivo (mouse con rotella Microsoft, gesti del touchpad del laptop , touchpad per laptop con scrollzone, mouse magico Apple, scrollball mouse Apple potente, touchpad Mac, ecc. ecc.).

E devono ignorare una varietà di effetti collaterali dalla configurazione del browser (ad es. Firefox mousewheel.enable_pixel_scrolling, chrome --scroll-pixels = 150), impostazioni del driver (ad es. Touchpad Synaptics) e configurazione del sistema operativo (impostazioni del mouse Windows, preferenze del mouse OSX, Impostazioni del pulsante X.org).


2

Questo è un problema con cui ho combattuto per alcune ore oggi, e non per la prima volta :(

Ho cercato di riassumere i valori su uno "scorrimento" e vedere come i diversi browser riportano i valori, e variano molto, con Safari che riporta un ordine di grandezza numeri più grandi su quasi tutte le piattaforme, Chrome riporta molto di più (come 3 volte di più ) rispetto a Firefox, Firefox essendo bilanciato a lungo termine ma abbastanza diverso tra le piattaforme su piccoli movimenti (su Ubuntu gnome, quasi solo +3 o -3, sembra riassumere eventi più piccoli e quindi inviare un grande "+3")

Le attuali soluzioni trovate in questo momento sono tre:

  1. Il già citato "usa solo il segno" che uccide qualsiasi tipo di accelerazione
  2. Annusa il browser fino alla versione e alla piattaforma secondaria e regola correttamente
  3. Qooxdoo ha recentemente implementato un algoritmo autoadattante, che sostanzialmente cerca di ridimensionare il delta in base al valore minimo e massimo ricevuto finora.

L'idea in Qooxdoo è buona, e funziona, ed è l'unica soluzione che ho trovato per essere completamente coerente tra i browser.

Purtroppo tende a rinormalizzare anche l'accelerazione. Se lo provi (nelle loro demo) e scorri su e giù alla massima velocità per un po ', noterai che lo scorrimento estremamente veloce o estremamente lento produce praticamente quasi la stessa quantità di movimento. Al contrario, se ricarichi la pagina e scorri solo molto lentamente, noterai che scorrerà abbastanza velocemente ".

Questo è frustrante per un utente Mac (come me) abituato a dare vigorosi colpi di scorrimento sul touchpad e aspettarsi di arrivare all'inizio o alla fine della cosa a scorrimento.

Ancora di più, poiché riduce la velocità del mouse in base al valore massimo ottenuto, più l'utente tenta di accelerarlo, più rallenterà, mentre un utente a "scorrimento lento" sperimenterà velocità piuttosto elevate.

Questo rende questa soluzione (altrimenti geniale) un'implementazione leggermente migliore della soluzione 1.

Ho portato la soluzione al plugin jwery mousewheel: http://jsfiddle.net/SimoneGianni/pXzVv/

Se ci giochi per un po ', vedrai che inizierai a ottenere risultati abbastanza omogenei, ma noterai anche che tende a + 1 / -1 valori abbastanza velocemente.

Ora sto lavorando per migliorarlo per rilevare meglio i picchi, in modo che non inviino tutto "fuori scala". Sarebbe anche bello ottenere anche un valore float compreso tra 0 e 1 come valore delta, in modo che vi sia un output coerente.


1

Non esiste assolutamente un modo semplice per normalizzare tutti gli utenti in tutti i sistemi operativi in ​​tutti i browser.

Peggiora le tue variazioni elencate - sulla mia configurazione di WindowsXP + Firefox3.6 la mia rotellina del mouse fa 6 per scorrimento di una tacca - probabilmente perché da qualche parte ho dimenticato di aver accelerato la rotella del mouse, sia nel sistema operativo o da qualche parte in circa: config

Tuttavia sto lavorando a un problema simile (con un'app simile tra l'altro, ma non canvas) e mi viene in mente semplicemente usando il segno delta di +1 / -1 e misurando nel tempo l'ultima volta che ha sparato, tu ha un tasso di accelerazione, cioè. se qualcuno scorre una volta contro più volte in pochi istanti (cosa che scommetterei è come lo fa Google Maps).

Il concetto sembra funzionare bene nei miei test, basta aggiungere qualcosa di meno di 100 ms all'accelerazione.


-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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.