跨浏览器标准化鼠标滚轮速度


147

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

在该代码中,我使用鼠标滚轮来放大/缩小HTML5 Canvas。我找到了一些可以标准化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上以相同数量的鼠标滚轮滚动获得相同的“增量”值?

这个问题是相关的,但是没有很好的答案。

编辑:进一步的调查显示一个滚动事件“向上”是:

                  | evt.wheelDelta | 细节
------------------ + ---------------- + ------------
  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(可能是错误的)
 Chrome v37 / OS X | 120 | 0
        IE9 / Win7 | 120 | 未定义
  Opera v11 / OS X | 40 | -1
  Opera v24 / OS X | 120 | 0
  Opera v11 / Win7 | 120 | -3
 Firefox v4 / Win7 | 未定义 -3
 Firefox v4 / OS X | 未定义 -1
Firefox v30 / OS X | 未定义 -1

此外,即使在缓慢移动的情况下,在OS X上使用MacBook触控板也会产生不同的结果:

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

所以问题是:

区分这种行为的最佳方法是什么(理想情况下,无需任何用户代理或OS嗅探)?


抱歉,我删除了我的问题。我正在写一个答案。在进一步讲解之前,您是否在谈论Mac OS X上Safari上的滚动?稍微滚动一下,它也会滚动一点,但是如果保持恒定的速率,它会逐渐变快吗?
Blender

@Blender我现在正在OS X上进行测试,是的,Safari是离群值,其缩放速度比Chrome快20倍。不幸的是,我没有安装物理鼠标,因此我的测试仅限于大约等距的距离和速度的两指轻扫。
Phrogz

我已经更新了有关OS X和Win7排名前5位浏览器行为的详细信息。这是一个雷区,OS X上的Chrome似乎是有问题的离群值。
Phrogz 2011年

@Phrogz不是e.wheelDelta/120吗?
森达维达斯

@ŠimeVidas是的,我复制并使用的代码显然是错误的。您可以在下面的答案中看到更好的代码。
Phrogz 2011年

Answers:


57

编辑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上平滑/加速滚动时,这不会产生很好的结果,也不能很好地处理鼠标滚轮快速移动(例如wheelDelta240)的情况,但是这种情况很少发生。由于此处描述的原因,现在该代码是此答案顶部显示的推荐技术。


@ŠimeVidas谢谢,这基本上我有什么,但我也占了上戏OS X的1/3差异
Phrogz

@Phrogz,您是否在2014年9月的更新版本中添加了所有OS X / 3?对于社区来说,这将是一个很棒的补充!
Basj 2014年

@Phrogz,这太好了。我没有Mac可以在这里测试...(即使我自己声誉不高,我也很乐意为此提供赏金;))
Basj 2014年

1
在Windows Firefox 35.0.1上,wheelDelta未定义,而detail始终为0,这使提供的代码失败。
Max Strater

1
@MaxStrater面对同样的问题,我添加了“ deltaY”来克服这个问题,(((evt.deltaY <0 || evt.wheelDelta>0) || evt.deltaY < 0) ? 1 : -1)但不确定如何进行质量检查。
布罗克

28

这是我疯狂的尝试,以生成跨浏览器的一致且标准化的增量(-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);

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


3
如果我可以再投票10次,我可以。非常感谢!
justnorris

您能否在演示中提供完整的功能代码(例如jsFiddle)?
adardesign 2013年

是否有理由将-object 缓存event在其中o
yckart

不,没有。该o变量在那里显示我们想要原始事件,而不是像jQuery或其他库这样的包装事件可以传递给事件处理程序。
smrtl 2013年

@smrtl您能解释一下n和n1吗?这些变量是做什么用的?
Om3ga 2014年

28

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

我已经在使用React构建的数据表上进行了测试,并且滚动时像黄油一样!

该解决方案可在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


3
尚未捆绑到原代码normalizeWHeel.js一个更直接的联系github.com/facebook/fixed-data-table/blob/master/src/...
罗宾Luiten

感谢@RobinLuiten,更新了原始帖子。
乔治,

这东西很棒。刚刚使用它,就像一个魅力!Facebook的好工作:)
perry

你能举一些使用它的例子吗?我试过了,它可以在FF中工作,但不能在Chrome或IE(11)中工作。谢谢
安德鲁

2
对于使用npm的任何人,都有一个随时可用的程序包,这些程序包已经从Facebook的固定数据表中提取了。有关更多详细信息,请参见此处npmjs.com/package/normalize-wheel
Simon Watson

11

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

基于此,我使此功能标准化了速度:

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

2
在macOS下,当前Safari和Firefox中的滚动速度不同。
Lenar Hoyt

6

另一个或多或少的独立解决方案...

不过,这并不需要考虑事件之间的时间。某些浏览器似乎总是以相同的增量触发事件,而在快速滚动时只会更快地触发事件。其他人确实会改变三角洲。可以想象一个自适应归一化器会考虑到时间,但是会涉及到一些麻烦且难以使用。

可在此处工作: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(); }
}

1
在Mac上使用Trackpad的Chrome上,此解决方案根本无法使用。
justnorris

@Norris我相信现在可以了。刚刚发现了这个问题,这里的示例适用于我使用Chrome的Macbook
哈里·莫雷诺

4

简单有效的解决方案:

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

3

要在触摸设备上支持缩放,请注册手势开始,手势更改和手势结束事件,并使用event.scale属性。您可以查看示例代码

对于Firefox 17,该onwheel事件计划受桌面版本和移动版本支持(根据onwheel上的MDN文档)。同样对于Firefox而言,特定于Ge​​cko的MozMousePixelScroll事件很有用(尽管由于Firefox中不赞成使用DOMMouseWheel事件,所以现在不建议使用此事件)。

对于Windows,驱动程序本身似乎会生成WM_MOUSEWHEEL,WM_MOUSEHWHEEL事件(以及可能用于触摸板平移的WM_GESTURE事件?)。这可以解释为什么Windows或浏览器似乎没有对鼠标滚​​轮事件值本身进行规范化(并且可能意味着您无法编写可靠的代码对这些值进行规范化)。

对于IE9和IE10的Internet Explorer中的onwheel不是 onmousewheel)事件支持,您还可以使用W3C标准 onwheel事件。但是,一个凹口的值可以不同于120(例如,使用此测试页,单个凹口在我的鼠标上变为111(而不是-120))。我写了另一篇文章,其中包含可能与之相关的其他详细信息。

基本上,在我自己的轮滑事件测试中(我正在尝试对滚动值进行标准化),我发现我获得了操作系统,浏览器供应商,浏览器版本,事件类型和设备(Microsoft斜轮鼠标,笔记本电脑触摸板手势)的不同值,带有滚动区的笔记本电脑触摸板,Apple魔术鼠标,Apple强大的鼠标滚动球,Mac触摸板等)。

并且必须忽略浏览器配置(例如Firefox mousewheel.enable_pixel_scrolling,chrome --scroll-pixels = 150),驱动程序设置(例如Synaptics触摸板)和操作系统配置(Windows鼠标设置,OSX鼠标首选项, X.org按钮设置)。


2

这是我今天一直在努力解决的问题,而不是第一次:(

我一直在尝试通过“滑动”来汇总值,看看不同的浏览器如何报告值,并且它们之间的差异很大,Safari几乎在所有平台上报告的数量级都更大,Chrome的报告数量更多(例如3倍多)。 )与firefox相比,firefox从长远来看是平衡的,但是在小动作上的平台之间却大不相同(在Ubuntu gnome上,几乎只有+3或-3,看起来像是汇总了较小的事件,然后发送了一个大的“ +3”)

现在找到的当前解决方案是三种:

  1. 已经提到的“仅使用符号”会扼杀任何形式的加速
  2. 嗅探浏览器直至次要版本和平台,并进行适当调整
  3. Qooxdoo最近实现了一种自适应算法,该算法基本上试图根据到目前为止收到的最小值和最大值来缩放增量。

Qooxdoo中的想法很好,并且可行,并且是我目前发现的唯一完全一致的跨浏览器解决方案。

不幸的是,它也倾向于使加速度重新归一化。如果尝试一下(在他们的演示中),并以最大速度上下滚动一会儿,您会注意到,极快或极慢的滚动基本上会产生几乎相同的移动量。相反,如果您重新加载页面并且仅非常缓慢地滑动,则会注意到它会快速滚动。”

对于习惯在触摸板上进行剧烈滚动滑动并期望到达滚动对象顶部或底部的Mac用户(如我)而言,这令人沮丧。

更重要的是,由于它会根据获得的最大值降低鼠标速度,因此您的用户尝试加快速度的次数越多,速度下降的速度也就越大,而“缓慢滚动”的用户将获得相当快的速度。

这使此解决方案(否则才是出色的解决方案)略微更好地实现了解决方案1。

我将解决方案移植到了jquery mousewheel插件上:http : //jsfiddle.net/SimoneGianni/pXzVv/

如果您玩了一会儿,您会发现您将开始获得相当均匀的结果,但是您也会注意到它趋向于+ 1 / -1值相当快。

我现在正在努力对其进行增强,以更好地检测峰值,以使它们不会“超出范围”发送所有内容。同时获得0到1之间的浮点值作为增量值也很不错,这样就可以获得连贯的输出。


1

绝对没有一种简单的方法可以在所有浏览器中的所有操作系统中的所有用户之间进行标准化。

它变得比您列出的版本还要糟糕-在我的WindowsXP + Firefox3.6设置上,我的鼠标滚轮每一次滚动6次-可能是因为我忘记了某个地方,无论是在OS中还是在某个地方,我都加速了鼠标滚轮:配置

但是我正在解决一个类似的问题(使用类似的应用程序顺便说一句,但不是画布),并且我只是通过使用+1 / -1的增量符号并在上次触发时随时间进行测量而发现它,有加速度,即。如果有人滚动一次 VS 多次在几分钟(我敢打赌,谷歌地图是如何做到的)。

这个概念在我的测试中似乎运作良好,只要使加速少于100毫秒即可。


-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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.