Normalización de la velocidad de la rueda del mouse en los navegadores

147

Para una pregunta diferente , compuse esta respuesta , incluido este código de muestra .

En ese código, uso la rueda del mouse para acercar / alejar un lienzo HTML5. Encontré un código que normaliza las diferencias de velocidad entre Chrome y Firefox. Sin embargo, el manejo del zoom en Safari es mucho, mucho más rápido que en cualquiera de ellos.

Aquí está el código que tengo actualmente:

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

¿Qué código puedo usar para obtener el mismo valor 'delta' para la misma cantidad de rueda del mouse en Chrome v10 / 11, Firefox v4, Safari v5, Opera v11 e IE9?

Esta pregunta está relacionada, pero no tiene una buena respuesta.

Editar : Una investigación adicional muestra que un evento de desplazamiento 'arriba' es:

                  El | evt.wheelDelta | evt.detail
------------------ + ---------------- + ------------
  Safari v5 / Win7 | 120 0 0
  Safari v5 / OS X | 120 0 0
  Safari v7 / OS X | 12 | 0 0
 Chrome v11 / Win7 | 120 0 0
 Chrome v37 / Win7 | 120 0 0
 Chrome v11 / OS X | 3 (!) | 0 (posiblemente incorrecto)
 Chrome v37 / OS X | 120 0 0
        IE9 / Win7 | 120 indefinido
  Opera v11 / OS X | 40 | -1
  Opera v24 / OS X | 120 0 0
  Opera v11 / Win7 | 120 -3
 Firefox v4 / Win7 | indefinido | -3
 Firefox v4 / OS X | indefinido | -1
Firefox v30 / OS X | indefinido | -1

Además, el uso del trackpad MacBook en OS X ofrece resultados diferentes incluso cuando se mueve lentamente:

  • En Safari y Chrome, el wheelDeltavalor es 3 en lugar de 120 para la rueda del mouse.
  • En Firefox , detailgeneralmente 2, a veces 1, pero cuando se desplaza muy lentamente, NO HAY MANEJO DE EVENTOS .

Entonces la pregunta es:

¿Cuál es la mejor manera de diferenciar este comportamiento (idealmente sin ningún agente de usuario o sistema operativo)?

Phrogz
fuente
Lo siento, borré mi pregunta. Estoy escribiendo una respuesta ahora mismo. Antes de ir mucho más lejos, ¿estás hablando del desplazamiento en Safari en Mac OS X? Cuando te desplazas un poco, se desplaza un poco, pero si mantienes una velocidad constante, ¿progresivamente se vuelve más rápido?
Blender
@Blender Estoy probando en OS X en este momento, y sí, Safari es el valor atípico que está haciendo zoom 20 veces más rápido que Chrome. Lamentablemente, no tengo un mouse físico conectado, por lo que mis pruebas se limitan a deslizar dos dedos de distancias y velocidades equivalentes.
Phrogz
He actualizado la pregunta con detalles sobre el comportamiento de los 5 principales navegadores en OS X y Win7. Es un campo minado, con Chrome en OS X que parece ser el problema atípico.
Phrogz
@Phrogz ¿No debería ser así e.wheelDelta/120?
Šime Vidas
@ ŠimeVidas Sí, el código que copié y que estaba usando era claramente incorrecto. Puedes ver un mejor código en mi respuesta a continuación .
Phrogz

Respuestas:

57

Editar septiembre de 2014

Dado que:

  • Las diferentes versiones del mismo navegador en OS X han arrojado valores diferentes en el pasado, y pueden hacerlo en el futuro, y eso
  • Usar el trackpad en OS X produce efectos muy similares a usar una rueda del mouse, pero da valores de eventos muy diferentes y, sin embargo, JS no puede detectar la diferencia de dispositivo

... Solo puedo recomendar el uso de este código simple de conteo basado en signos:

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

El intento original de ser correcto sigue.

Aquí está mi primer intento de un script para normalizar los valores. Tiene dos defectos en OS X: Firefox en OS X producirá valores 1/3 de lo que deberían ser, y Chrome en OS X producirá valores 1/40 de lo que deberían ser.

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

Puede probar este código en su propio navegador aquí: http://phrogz.net/JS/wheeldelta.html

Las sugerencias para detectar y mejorar el comportamiento en Firefox y Chrome en OS X son bienvenidas.

Editar : Una sugerencia de @Tom es simplemente contar cada llamada de evento como un solo movimiento, utilizando el signo de la distancia para ajustarlo. Esto no dará excelentes resultados con un desplazamiento suave / acelerado en OS X, ni manejará perfectamente los casos en que la rueda del mouse se mueva muy rápido (por ejemplo, wheelDeltaes 240), pero esto sucede con poca frecuencia. Este código es ahora la técnica recomendada que se muestra en la parte superior de esta respuesta, por las razones descritas allí.

Phrogz
fuente
@ ŠimeVidas Gracias, eso es básicamente lo que tengo, excepto que también tengo en cuenta la diferencia de 1/3 en Opera OS X.
Phrogz
@Phrogz, ¿tiene una versión actualizada en septiembre de 2014 con todos los OS X / 3 agregados? ¡Esta sería una gran adición para la comunidad!
Basj
@Phrogz, esto sería genial. No tengo una Mac aquí para probar ... (estaría feliz de dar una recompensa por eso, incluso si no tengo mucha reputación;))
Basj
1
En Windows Firefox 35.0.1, wheelDelta no está definido y los detalles siempre son 0, lo que hace que el código suministrado falle.
Max Strater
1
@MaxStrater Enfrenté el mismo problema, agregué "deltaY" para superar esto en una dirección como (((evt.deltaY <0 || evt.wheelDelta>0) || evt.deltaY < 0) ? 1 : -1)esa, aunque no estoy seguro de lo que QA descubre con eso.
Brock
28

Aquí está mi loco intento de producir un delta cruzado coherente y normalizado (-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);

Esto es totalmente empírico, pero funciona bastante bien en Safari 6, FF 16, Opera 12 (OS X) e IE 7 en XP

smrtl
fuente
3
Si pudiera votar otras 10 veces, podría. Muchas gracias!
justnorris
¿Puede tener el código funcional completo en una demostración (por ejemplo, jsFiddle)?
adardesign
¿Hay una razón para almacenar en caché el event-objeto de o?
yckart
No, no hay ninguno. La ovariable está ahí para mostrar que queremos que el evento original y no un evento envuelto como jQuery u otras bibliotecas puedan pasar a los controladores de eventos.
smrtl
@smrtl, ¿podría explicar ny n1? ¿Para qué son estas variables?
Om3ga
28

Nuestros amigos en Facebook crearon una gran solución para este problema.

¡He probado en una tabla de datos que estoy construyendo usando React y se desplaza como la mantequilla!

Esta solución funciona en una variedad de navegadores, en Windows / Mac, y en ambos usando 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 };
}

El código fuente se puede encontrar aquí: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js

Jorge
fuente
3
Un enlace más directo que no se ha incluido en el código original para normalizeWHeel.js github.com/facebook/fixed-data-table/blob/master/src/…
Robin Luiten
Gracias @RobinLuiten, actualizando la publicación original.
George
Esto es genial. ¡Acabo de usarlo y funciona como un encanto! Buen trabajo Facebook :)
perry
¿Podría dar algún ejemplo de cómo usarlo? Lo probé y funciona en FF pero no en Chrome o IE (11) ...? Gracias
Andrew
2
Para cualquiera que use npm hay un paquete listo para usar de solo este código ya extraído de la Tabla de datos fijos de Facebook. Ver aquí para más detalles npmjs.com/package/normalize-wheel
Simon Watson
11

Hice una tabla con diferentes valores devueltos por diferentes eventos / navegadores, teniendo en cuenta el wheel evento DOM3 que algunos navegadores ya admiten (tabla debajo).

Basado en eso, hice esta función para normalizar la velocidad:

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

Tabla de mousewheel, wheely DOMMouseScrolleventos:

| 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
fuente
2
Resultados en diferentes velocidades de desplazamiento en Safari y Firefox actuales bajo macOS.
Lenar Hoyt
6

Otra solución más o menos autónoma ...

Sin embargo, esto no toma tiempo entre eventos. Parece que algunos navegadores siempre activan eventos con el mismo delta, y solo los activan más rápido cuando se desplazan rápidamente. Otros varían los deltas. Uno puede imaginar un normalizador adaptativo que tenga en cuenta el tiempo, pero que se vuelva algo complicado y difícil de usar.

Trabajo disponible aquí: 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
fuente
1
Esta solución no funciona en absoluto con Chrome en Mac con Trackpad.
justnorris
@ Noror Creo que lo hace ahora. Acabo de encontrar esta pregunta y el ejemplo aquí funciona en mi macbook con Chrome
Harry Moreno
4

Solución simple y funcional:

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
fuente
3

Para soporte de zoom en dispositivos táctiles, regístrese para los eventos de inicio de gestos, cambio de gestos y final de gestos y use la propiedad event.scale. Puedes ver código de ejemplo para esto.

Para Firefox 17 onwheel, se planea que el evento sea compatible con versiones de escritorio y móviles (según los documentos de MDN en onwheel ). También para Firefox, tal vez el MozMousePixelScrollevento específico Gecko sea ​​útil (aunque presumiblemente esto ahora está en desuso ya que el evento DOMMouseWheel ahora está en desuso en Firefox).

Para Windows, el controlador en sí parece generar los eventos WM_MOUSEWHEEL, WM_MOUSEHWHEEL (¿y quizás el evento WM_GESTURE para la panorámica del panel táctil?). Eso explicaría por qué Windows o el navegador no parecen normalizar los valores del evento de la rueda del mouse (y podrían significar que no puede escribir código confiable para normalizar los valores).

Para el soporte de eventos onwheel( no en rueda de ratón) en Internet Explorer para IE9 e IE10, también puede usar el evento estándar W3C onwheel . Sin embargo, una muesca puede ser un valor diferente de 120 (por ejemplo, una muesca única se convierte en 111 (en lugar de -120) en mi mouse usando esta página de prueba ). Escribí otro artículo con otros detalles sobre eventos de rueda que podrían ser relevantes.

Básicamente, en mis propias pruebas para eventos de rueda (estoy tratando de normalizar los valores para el desplazamiento), descubrí que obtengo valores variables para el sistema operativo, el proveedor del navegador, la versión del navegador, el tipo de evento y el dispositivo (mouse de Microsoft con doble rueda, gestos del panel táctil de la computadora portátil , panel táctil portátil con zona de desplazamiento, mouse mágico de Apple, bola de desplazamiento del mouse poderoso de Apple, panel táctil de Mac, etc.

Y tiene que ignorar una variedad de efectos secundarios de la configuración del navegador (por ejemplo, Firefox mousewheel.enable_pixel_scrolling, chrome --scroll-pixels = 150), la configuración del controlador (por ejemplo, Synaptics touchpad) y la configuración del sistema operativo (configuración del mouse de Windows, preferencias del mouse OSX, Configuración del botón X.org).

robocat
fuente
2

Este es un problema con el que he estado luchando durante algunas horas hoy, y no por primera vez :(

He estado tratando de resumir los valores en un "deslizamiento" y ver cómo los diferentes navegadores informan valores, y varían mucho, con Safari informando un orden de magnitud de números más grandes en casi todas las plataformas, Chrome informa bastante más (como 3 veces más ) que firefox, firefox está equilibrado a largo plazo pero es bastante diferente entre plataformas en movimientos pequeños (en Ubuntu gnome, casi solo +3 o -3, parece que resume eventos más pequeños y luego envía un gran "+3")

Las soluciones actuales encontradas en este momento son tres:

  1. El ya mencionado "usa solo el signo" que mata cualquier tipo de aceleración
  2. Olfatee el navegador hasta la versión y plataforma menores, y ajústelo correctamente
  3. Qooxdoo implementó recientemente un algoritmo de autoadaptación, que básicamente trata de escalar el delta en función del valor mínimo y máximo recibido hasta el momento.

La idea en Qooxdoo es buena, y funciona, y es la única solución que actualmente considero un navegador cruzado completamente consistente.

Desafortunadamente, tiende a renormalizar también la aceleración. Si lo prueba (en sus demostraciones) y se desplaza hacia arriba y hacia abajo a la velocidad máxima durante un tiempo, notará que desplazarse extremadamente rápido o extremadamente lento básicamente produce casi la misma cantidad de movimiento. Por el contrario, si vuelve a cargar la página y solo desliza muy lentamente, notará que se desplazará bastante rápido ".

Esto es frustrante para un usuario de Mac (como yo) que solía deslizar vigorosamente el mouse sobre el panel táctil y esperaba llegar a la parte superior o inferior del elemento desplazado.

Aún más, dado que reduce la velocidad del mouse en función del valor máximo obtenido, cuanto más intente su usuario acelerar, más disminuirá la velocidad, mientras que un usuario de "desplazamiento lento" experimentará velocidades bastante rápidas.

Esto hace que esta solución (por lo demás brillante) sea una implementación ligeramente mejor de la solución 1.

Porté la solución al complemento jquery mousewheel: http://jsfiddle.net/SimoneGianni/pXzVv/

Si juegas con él por un tiempo, verás que comenzarás a obtener resultados bastante homogéneos, pero también notarás que tiende a valores + 1 / -1 bastante rápido.

Ahora estoy trabajando en mejorarlo para detectar picos mejor, para que no envíen todo "fuera de escala". También sería bueno obtener también un valor flotante entre 0 y 1 como el valor delta, para que haya una salida coherente.

Simone Gianni
fuente
1

Definitivamente no hay una manera simple de normalizar a todos los usuarios en todos los sistemas operativos en todos los navegadores.

Se pone peor que las variaciones enumeradas: en mi configuración de Windows XP + Firefox3.6 mi rueda del mouse hace 6 por desplazamiento de una muesca, probablemente porque en algún lugar que he olvidado he acelerado la rueda del mouse, ya sea en el sistema operativo o en algún lugar sobre: config

Sin embargo, estoy trabajando en un problema similar (con una aplicación similar por cierto, pero no lienzo) y se me ocurre simplemente usando el signo delta de +1 / -1 y midiendo con el tiempo la última vez que disparó, tener una tasa de aceleración, es decir. si alguien se desplaza una vez contra varias veces en unos momentos (lo que apostaría es cómo lo hace google maps).

El concepto parece funcionar bien en mis pruebas, solo agregue algo menos de 100 ms a la aceleración.

ck_
fuente
-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);
Matthieu Chavigny
fuente