Normalizzazione della velocità della rotellina del mouse attraverso i browser

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

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

Ecco il codice che attualmente ho:

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 utilizzare 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 (probabilmente 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 |  non definito |  -3
      Firefox v4 / OS X |  non definito |  -1
     Firefox v30 / OS X |  non definito |  -1
    

    Inoltre, l’uso del trackpad di MacBook su OS X dà risultati diversi anche quando si muove lentamente:

    • Su Safari e Chrome, wheelDelta un valore di 3 invece di 120 per la rotellina del mouse.
    • Su Firefox il detail solito è 2 , a volte 1 , ma quando si scorre molto lentamente NESSUN INCEPPATORE DI EVENTI A TUTTI .

    Quindi la domanda è:

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

    Modifica settembre 2014

    Dato che:

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

    … Posso solo consigliare l’utilizzo di 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 script per normalizzare i valori. Ha due difetti su OS X: Firefox su OS X produrrà valori 1/3 di quello che dovrebbero essere, e Chrome su OS X produrrà valori 1/40 di quello che 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 testare 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 da @ Tom è di contare semplicemente ogni chiamata di evento come una singola mossa, usando il segno della distanza per regolarla. Ciò non darà grandi risultati con lo scorrimento fluido / accelerato su OS X, né gestirà perfettamente casi quando la rotellina del mouse viene mossa molto velocemente (ad esempio wheelDelta è 240), ma questi si verificano raramente. Questo codice è ora la tecnica raccomandata mostrata all’inizio di questa risposta, per le ragioni qui descritte.

    Ecco il mio folle tentativo di produrre un delta coerente e normalizzato cross-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

    I nostri amici di Facebook hanno messo insieme una grande soluzione a questo problema.

    Ho provato su una tabella 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 può essere trovato qui: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js

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

    Sulla base di ciò ho fatto 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 eventi mousewheel , wheel e 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 | | | | | | | 

    Un’altra soluzione più o meno autonoma …

    Questo però non richiede tempo tra gli eventi. Alcuni browser sembrano sempre generare eventi con lo stesso delta e triggersrli più velocemente quando si scorre velocemente. Altri fanno variare i delta. Si può immaginare un normalizzatore adattativo che tenga conto del tempo, ma che sarebbe in qualche modo coinvolto e scomodo da usare.

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

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

    Per Firefox 17 è previsto che l’evento onwheel sia supportato dalle versioni desktop e mobile (come da documentazione MDN onwheel ). Anche per Firefox, forse l’evento specifico per MozMousePixelScroll 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 dell’evento della rotellina stessa (e potrebbe significare che non è ansible scrivere codice affidabile per normalizzare i valori).

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

    Fondamentalmente nei miei test per gli eventi wheel (sto provando a normalizzare i valori per lo scrolling), ho scoperto che ottengo vari valori per OS, browser, versione del browser, tipo di evento e dispositivo (mouse tiltwheel Microsoft, gesti del touchpad del laptop , touchpad portatile con scrollzone, mouse magico Apple, mighty mouse Apple scrollball, touchpad Mac, ecc. ecc.).

    E devi ignorare una serie di effetti collaterali dalla configurazione del browser (ad esempio Firefox mousewheel.enable_pixel_scrolling, chrome –scroll-pixels = 150), le impostazioni del driver (ad esempio Synaptics touchpad) e la configurazione del sistema operativo (impostazioni del mouse di Windows, preferenze del mouse OSX, Impostazioni del pulsante X.org).

    Questo è un problema con cui sto combattendo da alcune ore oggi, e non per la prima volta 🙁

    Ho cercato di riassumere i valori in un “passaggio” e di vedere come i diversi browser riportano i valori e variano molto, con Safari che riporta numeri di ordine di grandezza più grandi su quasi tutte le piattaforms, Chrome che riporta molto di più (come 3 volte di più ) di firefox, firefox è bilanciato a lungo termine ma abbastanza diverso tra piattaforms su piccoli movimenti (su Ubuntu gnome, quasi solo +3 o -3, sembra che riassuma eventi più piccoli e poi invii un grande “+3”)

    Le soluzioni attuali trovate al 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 minori e regola correttamente
    3. Qooxdoo ha recentemente implementato un algoritmo autoadattativo, che fondamentalmente cerca di scalare 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 attualmente per essere un cross browser completamente coerente.

    Sfortunatamente tende a rinormalizzare anche l’accelerazione. Se lo provi (nelle loro demo) e scorri verso l’alto e verso il basso alla massima velocità per un po ‘, noterai che lo scorrimento estremamente veloce o estremamente lento produce praticamente 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) usato per dare vigorosi scroll scroll sul touchpad e aspettandosi di arrivare in cima o in fondo alla cosa a scorrimento.

    Inoltre, poiché ridimensiona 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à abbastanza elevate.

    Ciò rende questa (altrimenti brillante) soluzione un’implementazione leggermente migliore della soluzione 1.

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

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

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

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

    Diventa peggio delle tue varianti elencate – sulla mia configurazione di WindowsXP + Firefox3.6 la mia rotellina fa 6 per ogni notch scroll – probabilmente perché da qualche parte ho dimenticato di aver accelerato la rotellina del mouse, nel sistema operativo o in qualche parte in giro: config

    Tuttavia sto lavorando su un problema simile (con un’app simile btw, 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, avere un tasso di accelerazione, es. se qualcuno scorre una volta contro più volte in pochi istanti (cosa che scommetterei è come fa Google Maps).

    Il concetto sembra funzionare bene nei miei test, basta fare qualcosa di meno di 100ms per accelerare.

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

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