javascript - Normalizando a velocidade do scroll do mouse entre os navegadores
Para uma pergunta diferente, eu compus esta resposta, incluindo este código de exemplo.
Nesse código, eu uso a roda do mouse para dar zoom in/out em um Canvas HTML5. Eu encontrei algum código que normaliza as diferenças de velocidade entre o Chrome e o Firefox. No entanto, o manuseio de zoom no Safari é muito, muito mais rápido do que em qualquer um desses.
Aqui está o código que eu tenho atualmente:
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
Qual código posso usar para obter o mesmo valor de 'delta' para a mesma quantidade de rolagem do mouse no Chrome v10/11, Firefox v4, Safari v5, Opera v11 e IE9?
Esta pergunta está relacionada, mas não tem uma boa resposta.
Editar: Investigações adicionais mostram que um evento de rolagem 'para cima' é:
| 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 (possibly wrong)
Chrome v37/OS X | 120 | 0
IE9/Win7 | 120 | undefined
Opera v11/OS X | 40 | -1
Opera v24/OS X | 120 | 0
Opera v11/Win7 | 120 | -3
Firefox v4/Win7 | undefined | -3
Firefox v4/OS X | undefined | -1
Firefox v30/OS X | undefined | -1
Além disso, usar o trackpad do MacBook no OS X produz resultados diferentes, mesmo quando se move lentamente:
- No Safari e no Chrome, o
wheelDeltaé um valor de 3 em vez de 120 para o scroll do mouse. - No Firefox, o
detailgeralmente é2, às vezes1, mas ao rolar muito devagar NENHUM TRATADOR DE EVENTO DISPARA.
Então a questão é:
Qual é a melhor maneira de diferenciar esse comportamento (idealmente sem qualquer detecção de agente de usuário ou sistema operacional)?
Resposta #1
Editar setembro de 2014
Dado que:
- Diferentes versões do mesmo navegador no OS X produziram valores diferentes no passado, e podem fazê-lo no futuro, e que
- Usar o trackpad no OS X produz efeitos muito semelhantes aos de usar a roda do mouse, mas fornece valores de evento muito diferentes, e ainda assim a diferença do dispositivo não pode ser detectada pelo JS
…Só posso recomendar o uso deste código simples de contagem baseado em sinais:
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
Tentativa original de estar correta segue.
Aqui está minha primeira tentativa de um script para normalizar os valores. Ele tem duas falhas no OS X: o Firefox no OS X produzirá valores de 1/3 do que deveriam ser, e o Chrome no OS X produzirá valores de 1/40 do que deveriam 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
};
Você pode testar este código no seu próprio navegador aqui: http://phrogz.net/JS/wheeldelta.html
Sugestões para detectar e melhorar o comportamento no Firefox e Chrome no OS X são bem-vindas.
Editar: Uma sugestão de @Tom é simplesmente contar cada chamada de evento como um único movimento, usando o sinal da distância para ajustá-lo. Isso não dará ótimos resultados durante o deslocamento suave/acelerado no OS X, nem lidará perfeitamente com casos em que a roda do mouse é movida muito rapidamente (por exemplo, wheelDelta é 240), mas isso acontece raramente. Este código agora é a técnica recomendada mostrada no topo desta resposta, pelos motivos descritos lá.
Resposta #2
Nossos amigos do Facebook criaram uma ótima solução para este problema.
Eu testei em uma tabela de dados que estou construindo usando React e ela rola como manteiga!
Esta solução funciona em uma variedade de navegadores, no Windows/Mac, e tanto usando touchpad quanto 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 };
}
O código-fonte pode ser encontrado aqui: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js
Resposta #3
Aqui está minha tentativa louca de produzir um delta coerente e normalizado entre navegadores ( -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);
Isto é totalmente empírico, mas funciona muito bem no Safari 6, FF 16, Opera 12 (OS X) e IE 7 no XP
Resposta #4
Eu fiz uma tabela com diferentes valores retornados por diferentes eventos/navegadores, levando em conta o evento DOM3 wheel que alguns navegadores já suportam (tabela abaixo).
Com base nisso, eu fiz esta função para normalizar a velocidade:
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;
}
Tabela para eventos 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 | | | | | | |
