web-dev-qa-db-fra.com

Normaliser la vitesse de la molette de la souris sur les navigateurs

Pour une question différente j'ai composé cette réponse , y compris cet exemple de code .

Dans ce code, j'utilise la molette de la souris pour effectuer un zoom avant/arrière sur un canevas HTML5. J'ai trouvé du code qui normalise les différences de vitesse entre Chrome et Firefox. Cependant, la gestion du zoom dans Safari est beaucoup, beaucoup plus rapide que dans les deux cas.

Voici le code que j'ai actuellement:

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

Quel code puis-je utiliser pour obtenir la même valeur "delta" pour la même quantité de molette de la souris que sur les versions 10/11, Firefox 4, Safari 5, Opera v11 et IE9?

_ {Cette question est liée, mais n'a pas de bonne réponse.} _

Edit: une enquête plus approfondie montre qu'un événement de défilement 'en haut' est:

 | 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 (éventuellement faux) 
 Chrome v37/OS X | 120 | 0 
 IE9/Win7 | 120 | indéfini
 Opera v11/OS X | 40 | -1
 Opera v24/OS X | 120 | 0 
 Opera v11/Win7 | 120 | -3 
 Firefox v4/Win7 | non défini | -3 
 Firefox v4/OS X | non défini | -1 
 Firefox v30/OS X | non défini | -1

De plus, l’utilisation du trackpad MacBook sous OS X donne des résultats différents même en cas de déplacement lent:

  • Sur Safari et Chrome, la wheelDelta est une valeur de 3 au lieu de 120 pour la molette de la souris.
  • Sur Firefox, la detail est généralement 2, parfois 1, mais en cas de défilement très lent NO EVAND HANDLER LANCE AT ALL.

La question est donc:

Quel est le meilleur moyen de différencier ce comportement (idéalement sans aucun agent d'utilisateur ou reniflement de système d'exploitation)?

130
Phrogz

Edit Septembre 2014

Étant donné que:

  • Différentes versions du même navigateur sur OS X ont donné des valeurs différentes dans le passé et peuvent le faire dans le futur.
  • L'utilisation du trackpad sous OS X donne des effets effets très similaires à ceux d'une molette de la souris, mais donne un événement très différent valeurs, mais la différence entre périphériques ne peut pas être détectée par JS.

… Je ne peux que recommander l'utilisation de ce code de comptage simple:

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

_ {La tentative originale pour être correcte suit.

Voici ma première tentative de script pour normaliser les valeurs. Il a deux défauts sur OS X: Firefox sous OS X produira des valeurs 1/3 de ce qu’elles devraient être, et Chrome sous OS X produira des valeurs de 1/40 de ce qu’elles devraient être.

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

Vous pouvez tester ce code sur votre propre navigateur ici: http://phrogz.net/JS/wheeldelta.html

_ {Les suggestions pour détecter et améliorer le comportement sur Firefox et Chrome sous OS X sont les bienvenues.} _

Edit: une suggestion de @Tom est de simplement compter chaque appel d'événement comme un seul mouvement, en utilisant le signe de la distance pour l'ajuster. Cela ne donnera pas d'excellents résultats sous le défilement régulier/accéléré sous OS X, ni ne fonctionnera parfaitement lorsque la molette de la souris est déplacée très rapidement (par exemple, wheelDelta vaut 240), mais cela arrive rarement. Ce code est maintenant la technique recommandée indiquée en haut de cette réponse, pour les raisons qui y sont décrites.

49
Phrogz

Voici ma tentative folle de produire un delta cohérent et normalisé pour tous les navigateurs (-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);

Ceci est totalement empirique mais fonctionne assez bien sur Safari 6, FF 16, Opera 12 (OS X) et IE 7 sur XP

27
smrtl

Nos amis de Facebook ont ​​mis au point une excellente solution à ce problème. 

J'ai testé sur une table de données que je construis en utilisant React et il défile comme du beurre! 

Cette solution fonctionne sur divers navigateurs, sur Windows/Mac et à l’aide du trackpad/de la souris. 

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

Le code source peut être trouvé ici: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js

19
George

J'ai créé un tableau avec différentes valeurs renvoyées par différents événements/navigateurs, en prenant en compte l'événement DOM3wheel que certains navigateurs prennent déjà en charge (tableau sous).

Sur cette base, j'ai créé cette fonction pour normaliser la vitesse:

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

Tableau pour les événements mousewheel, wheel et 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     |                |                |                |           |             |           |
10
Sergio

Une autre solution plus ou moins autonome ...

Cela ne prend pas de temps entre les événements en compte cependant. Certains navigateurs semblent toujours déclencher des événements avec le même delta et les déclencher plus rapidement lorsque vous faites défiler rapidement. D'autres varient les deltas. On peut imaginer un normalisateur adaptatif prenant en compte le temps, mais qui serait plutôt compliqué et difficile à utiliser.

Travailler disponible ici: 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(); }
}
6
Marijn

Pour la prise en charge du zoom sur les appareils tactiles, enregistrez-vous pour les événements gesturestart, gesturechange et gestureend et utilisez la propriété event.scale. Vous pouvez voir exemple de code pour cela.

Pour Firefox 17, il est prévu que l'événement onwheel soit pris en charge par les versions pour ordinateur de bureau et mobile (selon la documentation MDN sur onwheel ). De plus, pour Firefox, l'événement MozMousePixelScroll spécifique à Gecko est peut-être utile (bien que ce soit probablement obsolète, l'événement DOMMouseWheel étant désormais obsolète dans Firefox).

Pour Windows, le pilote lui-même semble générer les événements WM_MOUSEWHEEL, WM_MOUSEHWHEEL (et peut-être l'événement WM_GESTURE pour le panoramique du pavé tactile?). Cela expliquerait pourquoi Windows ou le navigateur ne semble pas normaliser les valeurs de l'événement mousewheel (et peut signifier que vous ne pouvez pas écrire de code fiable pour normaliser les valeurs).

Pour l'événement onwheel (not onmousewheel) dans Internet Explorer pour IE9 et IE10, vous pouvez également utiliser l'événement W3C standardonwheel. Cependant, une encoche peut être différente de 120 (par exemple, une encoche devient 111 (au lieu de -120) sur ma souris en utilisant cette page de test ). J'ai écrit un autre article avec d'autres détails. Décrivez les événements qui pourraient être pertinents.

Essentiellement, lors de mes propres tests d’événements de roue (j’essaie de normaliser les valeurs pour le défilement), j’ai constaté des valeurs variables pour le système d’exploitation, le fournisseur du navigateur, la version du navigateur, le type d’événement et le périphérique (souris à molette Microsoft, gestes du pavé tactile de l’ordinateur portable). , pavé tactile pour ordinateur portable avec zone de défilement, souris magique Apple, molette de défilement puissante souris Apple, pavé tactile Mac, etc.).

Et devez ignorer divers effets secondaires de la configuration du navigateur (par exemple, Firefox mousewheel.enable_pixel_scrolling, chrome --scroll-pixels = 150), les paramètres du pilote (par exemple, le pavé tactile Synaptics) et la configuration du système d'exploitation (paramètres de souris Windows, préférences de souris OSX, Bouton X.org).

3
robocat

C'est un problème avec lequel je me bats depuis quelques heures aujourd'hui, et pas pour la première fois :(

J'ai essayé de résumer les valeurs après un "balayage" et de voir comment les différents navigateurs signalent les valeurs, et elles varient beaucoup, Safari signalant des ordres de grandeur plus grands sur presque toutes les plates-formes, Chrome rapportant beaucoup plus (comme 3 fois plus ) que firefox, firefox étant équilibré sur le long terme mais assez différent d’une plate-forme à l’autre sur de petits mouvements (sur Ubuntu gnome, presque seulement +3 ou -3, cela résume des événements plus petits et envoie un gros "+3")

Les solutions actuelles trouvées à l'heure actuelle sont trois:

  1. Le déjà mentionné "utilise uniquement le signe" qui tue tout type d'accélération
  2. Sniff le navigateur jusqu'à la version mineure et la plate-forme, et ajustez correctement
  3. Qooxdoo a récemment mis en place un algorithme auto-adaptatif, qui essaie fondamentalement de redimensionner le delta en fonction des valeurs minimale et maximale reçues jusqu'à présent.

L’idée de Qooxdoo est bonne et fonctionne. C’est la seule solution que j’ai trouvée à l’heure actuelle comme étant un navigateur multirisque totalement cohérent. 

Malheureusement, il a tendance à renormaliser aussi l'accélération. Si vous essayez (dans leurs démos), et faites défiler à la vitesse maximale pendant un certain temps, vous remarquerez que le défilement extrêmement rapide ou extrêmement lent produit essentiellement le même nombre de mouvements. Au contraire, si vous rechargez la page et ne faites que glisser très lentement, vous remarquerez qu’elle défilera assez rapidement ".

C'est frustrant pour un utilisateur de Mac (comme moi) habitué à balayer le défilement sur le pavé tactile et à s'attendre à atteindre le haut ou le bas du défilement.

De plus, étant donné que la vitesse de la souris est réduite en fonction de la valeur maximale obtenue, plus votre utilisateur essaie de l'accélérer, plus elle ralentit, alors qu'un utilisateur à "défilement lent" connaîtra des vitesses assez rapides.

Cela rend cette solution (sinon brillante) une implémentation légèrement meilleure de la solution 1. 

J'ai porté la solution au plugin jquery mousewheel: http://jsfiddle.net/SimoneGianni/pXzVv/

Si vous jouez pendant un certain temps, vous constaterez que vous obtiendrez des résultats assez homogènes, mais vous remarquerez également que la tendance est rapide.

Je travaille actuellement à l’améliorer pour mieux détecter les pics, afin qu’ils n’envoient pas tout "hors échelle". Il serait également intéressant d’obtenir également une valeur de flottement comprise entre 0 et 1 en tant que valeur delta, afin d’obtenir une sortie cohérente.

2
Simone Gianni

Il n’existe aucun moyen simple de normaliser tous les utilisateurs, tous les systèmes d’exploitation, tous navigateurs confondus.

Cela devient pire que vos variantes énumérées - sur ma configuration WindowsXP + Firefox3.6, ma molette de souris fait 6 par défilement d'un cran - probablement parce que quelque part j'ai oublié la molette de la souris, que ce soit dans le système d'exploitation ou ailleurs: config

Cependant, je travaille sur un problème similaire (avec une application similaire, mais pas sur une toile) et cela me vient à l’esprit en utilisant simplement le signe delta +1/-1 et mesurant au fil du temps lors du dernier déclenchement. , vous aurez un taux d'accélération, c'est à dire. si quelqu'un fait défiler une fois vs plusieurs fois en quelques instants (je parierais que Google Maps le fait).

Le concept semble bien fonctionner dans mes tests, il suffit d’ajouter tout ce qu’il reste à moins de 100 ms à l’accélération.

1
ck_

Solution simple et efficace:

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