正常化浏览器中的鼠标轮速度

对于 另一个问题,我编写了 这个答案,包括 这个示例代码

在这段代码中,我使用鼠标滚轮来放大/缩小 HTML5画布。我发现了一些代码可以标准化 Chrome 和 Firefox 之间的速度差异。然而,Safari 中的缩放处理比这两者都要快得多。

下面是我目前的代码:

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

在 Chrome v10/11、 Firefox v4、 Safari v5、 Opera v11和 IE9中,我可以使用什么代码来获得相同数量的鼠标滚轮相同的“ delta”值?

这个问题是有联系的,但是没有好的答案。

编辑 : 进一步的研究表明,一个滚动事件‘ up’是:

| 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

此外,在 OS X 上使用 MacBook 轨迹板,即使移动缓慢,也会得到不同的结果:

  • 在 Safari 和 Chrome 上,鼠标轮的 wheelDelta值是3,而不是120。
  • 在 Firefox 上,detail通常是 2,有时是 1,但是当滚动非常慢的时候是 根本没有事件处理程序发生火灾

所以问题是:

区分这种行为的最佳方法是什么(理想情况下不需要任何用户代理或操作系统嗅探) ?

80883 次浏览

编辑2014年9月

鉴于此:

  • 同一个浏览器在 OS X 上的不同版本在过去产生了不同的价值,并且在将来可能会产生不同的价值
  • 在 OS X 上使用轨迹板会产生非常类似于使用鼠标滚轮的 影响,但是会产生非常不同的事件 价值观,而且设备的差异不能被 JS 检测到

... 我只能推荐使用这个简单的,基于符号的计数代码:

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

原来的尝试是正确的如下。

这是我第一次尝试使用脚本来规范化这些值。它在 OS X 上有两个缺陷: OS X 上的 Firefox 产生的值是应该的1/3,而 OS X 上的 Chrome 产生的值是应该的1/40。

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

您可以在自己的浏览器上测试这段代码: http://phrogz.net/JS/wheeldelta.html

对于在 OS X 上检测和改进 Firefox 和 Chrome 的行为,欢迎提出建议。

编辑 : 来自@Tom 的一个建议是简单地将每个事件调用计算为一个单独的步骤,使用距离符号来调整它。在 OS X 平滑/加速滚动的情况下,这不会产生很好的结果,在鼠标滚轮移动非常快(例如 wheelDelta是240)的情况下,也不会处理得很好,但这种情况很少发生。由于这里描述的原因,这段代码现在是这个答案顶部所示的推荐技术。

这是我今天为之奋斗了几个小时的问题,而且不是第一次:

我一直试图通过“滑动”来总结数值,看看不同的浏览器是如何报告数值的,它们有很大的不同,几乎所有平台的 Safari 报告数量级都更大,Chrome 报告的数值比火狐多得多(比火狐多3倍) ,火狐在长期上是平衡的,但在小的移动平台上差异很大(在 Ubuntu gnome 上,几乎只有 + 3或 -3,似乎它总结小的事件,然后发送一个大的“ + 3”)

目前找到的解决办法有三个:

  1. 已经提到的“只使用符号”可以消除任何形式的加速度
  2. 嗅嗅浏览器的次要版本和平台,并适当调整
  3. Qooxdoo 最近实现了一个自适应算法,该算法基本上试图根据目前收到的最小值和最大值来缩放增量。

这个想法在 Qooxdoo 很好,也很有效,是我目前发现的唯一一个完全一致的跨浏览器解决方案。

不幸的是,它也倾向于重整化加速度。如果你尝试一下(在他们的演示中) ,并且以最快的速度上下滚动一段时间,你会发现滚动极快或极慢基本上产生了几乎相同的运动量。相反,如果你重新加载页面,只是非常缓慢地滑动,你会发现它会滚动得非常快。”。

对于一个 Mac 用户(比如我)来说,这是令人沮丧的,因为他们习惯于在触摸板上使劲地滑动滚动条,希望能够到达滚动条的顶部或底部。

更重要的是,由于它可以根据获得的最大值来降低鼠标速度,用户试图加快速度的次数越多,它的速度就会越慢,而“慢滚动”用户则会体验到相当快的速度。

这使得这个解决方案成为解决方案1的一个稍微好一点的实现。

我将解决方案移植到 jquery 鼠标轮插件: http://jsfiddle.net/SimoneGianni/pXzVv/

如果你使用它一段时间,你会看到你会开始得到相当同质的结果,但你也会注意到它往往 + 1/-1值相当快。

我现在正在努力提高它,以便更好地检测峰值,这样他们就不会发送一切“超出规模”。还可以获得一个介于0和1之间的浮点值作为 delta 值,这样就有了一致的输出。

在所有浏览器的所有操作系统的所有用户之间绝对没有简单的标准化方法。

它比你列出的变化更糟糕——在我的 WindowsXP + Firefox3.6设置中,我的鼠标滚轮每一级滚动6次——可能是因为我忘记在操作系统或者 about: config 的某个地方加速了鼠标滚轮

然而我正在处理一个类似的问题(使用一个类似的应用程序,但不是画布) ,我发现只要使用 + 1/-1和 随着时间的推移测量的增量符号,上次发射时,你就会有一个加速率,即。如果有人滚动 一次在一会儿的时间里好几次(我敢打赌是如何谷歌地图做到这一点)。

在我的测试中,这个概念似乎很有效,只要加速小于100毫秒就可以了。

另一个或多或少自给自足的解决方案..。

不过,这并不需要考虑事件之间的时间间隔。有些浏览器似乎总是以相同的增量触发事件,只是在快速滚动时触发速度更快。其他的三角洲则各不相同。我们可以想象一个自适应标准化程序,它会考虑到时间,但是这会有点复杂,使用起来很笨拙。

工作在这里: 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(); }
}

对于触摸设备上的缩放支持,请注册 Gesturestart、 Gesturechange 和 Gestureend 事件,并使用 event.scale 属性。您可以看到 示例代码

对于 Firefox 17,onwheel事件计划由桌面和移动版本支持(根据 MDN 文件在轮子上)。同样,对于 Firefox,Gecko 特定的 MozMousePixelScroll事件可能也很有用(尽管可能现在已经不推荐使用这个事件,因为在 Firefox 中已经不推荐使用 DOMMouseWheels 事件)。

对于 Windows,驱动程序本身似乎生成了 WM _ MOUSEWHEEL,WM _ MOUSEHWHEEL 事件(也许还有用于触摸板平移的 WM _ GESTURE 事件?).这就解释了为什么 Windows 或者浏览器似乎没有规范化鼠标轮事件值本身(并且可能意味着你不能编写可靠的代码来规范化这些值)。

对于 IE9和 IE10的 onwheel(没有 onmouseeler)事件 Internet Explorer 的支持,还可以使用 W3C 标准 onwheel事件。然而,一个缺口可以是一个不同于120的值(例如,在我的鼠标 使用此测试页上,一个缺口变成了111(而不是 -120))。我写的 另一篇文章与其他细节车轮事件,可能是相关的。

基本上,在我自己的滚轮事件测试中(我试图将滚轮的值标准化) ,我发现操作系统、浏览器供应商、浏览器版本、事件类型和设备(微软倾斜滚轮鼠标、笔记本触摸板手势、笔记本触摸板带滚轮区、苹果魔术鼠标、 Apple Mighty Mouse 滚轮、 Mac 触摸板等等) ,我得到了不同的值。

而且不得不忽略浏览器配置的各种副作用(例如 Firefox 鼠标滚动,chrome ——滚动像素 = 150) ,驱动程序设置(例如 Synaptics 触摸板)和操作系统配置(Windows 鼠标设置,OSX 鼠标首选项,X.org 按钮设置)。

下面是我的一个疯狂尝试: 生成一个跨浏览器连贯的标准化 delta (-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);

这完全是经验性的,但是在 Safari 6,FF 16,Opera 12(OS X)和 IE 7 XP 上都很好用

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

我制作了一个表,不同的事件/浏览器返回不同的值,考虑到 DOM3 wheel事件,一些浏览器已经支持(下表)。

在此基础上,我做了这个函数来标准化速度:

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

mousewheelwheelDOMMouseScroll项目表:

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

我们在 Facebook 的朋友为这个问题提出了一个很好的解决方案。

我已经测试了一个数据表,我正在建立使用反应和它像黄油滚动!

这个解决方案适用于各种浏览器,Windows/Mac,以及使用轨迹板/鼠标的浏览器。

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

源代码可以在这里找到: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js

简单可行的解决方案:

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