跨浏览器规范鼠标滚轮速度

Posted

技术标签:

【中文标题】跨浏览器规范鼠标滚轮速度【英文标题】:Normalizing mousewheel speed across browsers 【发布时间】:2011-07-28 12:12:53 【问题描述】:

对于a different question,我编写了this answer,包括this sample code。

在该代码中,我使用鼠标滚轮放大/缩小 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 上滚动相同数量的鼠标滚轮时,我可以使用什么代码获得相同的“增量”值?

This question 是相关的,但没有好的答案。

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

| evt.wheelDelta | evt.detail ------------------+----------------+------------ Safari v5/Win7 | 120 | 0 Safari v5/OS X | 120 | 0 Safari v7/OS X | 12 | 0 铬 v11/Win7 | 120 | 0 铬 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 歌剧v11/Win7 | 120 | -3 火狐v4/Win7 |未定义 | -3 火狐v4/OS X |未定义 | -1 火狐v30/OS X |未定义 | -1

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

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

所以问题是:

区分这种行为的最佳方法是什么(最好没有任何用户代理或操作系统嗅探)?

【问题讨论】:

抱歉,我删除了我的问题。我现在正在写一个答案。在我进一步讨论之前,您是在谈论 Mac OS X 上 Safari 上的滚动吗?当您滚动一点时,它会滚动一点,但是如果您保持恒定的速度,它会逐渐变快? @Blender 我现在正在 OS X 上进行测试,是的,Safari 是异常值,它的缩放速度比 Chrome 快 20 倍。不幸的是,我没有连接物理鼠标,所以我的测试仅限于用两根手指滑动约等距离和速度。 我更新了这个问题,详细介绍了 OS X 和 Win7 中排名前 5 位的浏览器的行为。这是一个雷区,OS X 上的 Chrome 似乎是有问题的异常值。 @Phrogz 不应该是e.wheelDelta/120吗? @ŠimeVidas 是的,我复制和使用的代码显然是错误的。你可以在my answer below看到更好的代码。 【参考方案1】:

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),但这些情况很少发生。由于那里描述的原因,此代码现在是此答案顶部显示的推荐技术。

【讨论】:

@ŠimeVidas 谢谢,这基本上就是我所拥有的,除了我还考虑了 Opera OS X 上的 1/3 差异。 @Phrogz,你有 2014 年 9 月的更新版本,添加了所有 OS X /3 吗?这对社区来说将是一个很好的补充! @Phrogz,这会很棒。我这里没有要测试的 Mac……(即使我自己没有太多名声,我也很乐意为此提供赏金;)) 在 windows Firefox 35.0.1 上,wheelDelta 未定义且 detail 始终为 0,这使得提供的代码失败。 @MaxStrater 面临同样的问题,我添加了“deltaY”来克服这个问题,就像(((evt.deltaY &lt;0 || evt.wheelDelta&gt;0) || evt.deltaY &lt; 0) ? 1 : -1) 不确定 QA 会发现什么。【参考方案2】:

我们在 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

【讨论】:

一个更直接的链接,没有绑定到 normalizeWHeel.js 的原始代码github.com/facebook/fixed-data-table/blob/master/src/… 感谢@RobinLuiten,更新原帖。 这东西太棒了。刚刚使用它,就像一个魅力!干得好 Facebook :) 对于使用 npm 的任何人,都可以使用已从 Facebook 的固定数据表中提取的代码包。有关详细信息,请参阅此处npmjs.com/package/normalize-wheel 我将它用于缩放控制,但对我来说,使用 Macbook 触控板和通过此代码使用 Logitech MX Anywhere 2 鼠标之间存在巨大差异。【参考方案3】:

这是我疯狂尝试产生一个跨浏览器连贯和规范化的增量(-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) 和 XP 上的 IE 7 上运行良好

【讨论】:

如果我可以再投票 10 次。非常感谢! 能否在演示中提供完整的功能代码(例如 jsFiddle)? 是否有理由缓存 event-object 在o 中? 不,没有。 o 变量用于显示我们想要原始事件,而不是像 jQuery 或其他库这样的包装事件可能传递给事件处理程序。 我认为您还应该在var 中声明f【参考方案4】:

我制作了一个表格,其中包含不同事件/浏览器返回的不同值,taking into account the 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     |                |                |                |           |             |           |

【讨论】:

导致macOS下当前Safari和Firefox的滚动速度不同。【参考方案5】:

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

不过,这并不需要考虑事件之间的时间。一些浏览器似乎总是以相同的增量触发事件,并且在快速滚动时更快地触发它们。其他人确实改变了增量。可以想象一种将时间考虑在内的自适应规范化器,但使用起来会有些复杂和尴尬。

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

【讨论】:

此解决方案根本不适用于带有触控板的 Mac 上的 Chrome。 @Norris 我相信现在可以了。刚刚发现这个问题,这里的例子适用于我的带有 chrome 的 macbook【参考方案6】:

简单有效的解决方案:

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;

【讨论】:

【参考方案7】:

这是我今天已经解决了几个小时的问题,而且不是第一次 :(

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

目前找到的解决方案有以下三种:

    已经提到的“仅使用标志”可以消除任何类型的加速 将浏览器嗅探到次要版本和平台,并适当调整 Qooxdoo 最近实现了一种自适应算法,该算法基本上尝试根据目前收到的最小值和最大值来缩放增量。

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

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

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

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

这使得这个(否则很棒的)解决方案成为解决方案 1 的更好的实现。

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

如果您使用它一段时间,您会发现您将开始获得相当均匀的结果,但您也会注意到它趋向于非常快地 +1/-1 值。

我现在正在努力增强它以更好地检测峰值,这样它们就不会发送“超出范围”的所有内容。获得一个介于 0 和 1 之间的浮点值作为 delta 值也很好,这样就有一个连贯的输出。

【讨论】:

【参考方案8】:

要在触摸设备上支持缩放,请注册gesturestart、gesturechange 和gestureend 事件并使用event.scale 属性。你可以看到example code这个。

对于 Firefox 17,桌面和移动版本计划支持 onwheel 事件(根据 MDN docs on onwheel)。同样对于 Firefox,Gecko 特定的 MozMousePixelScroll 事件可能很有用(尽管现在可能已弃用,因为 DOMMouseWheel 事件现在在 Firefox 中已弃用)。

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

对于onwheel (not onmousewheel) 事件 support in Internet Explorer 对于 IE9 和 IE10,您也可以使用 W3C standard onwheel 事件。然而,一个缺口可以是不同于 120 的值(例如,在我的鼠标 using this test page 上,一个缺口变成 111(而不是 -120))。我写了another article 以及可能相关的其他详细信息。

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

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

【讨论】:

【参考方案9】:

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

它比你列出的变体更糟糕 - 在我的 WindowsXP+Firefox3.6 设置中,我的鼠标滚轮每一个格滚动 6 次 - 可能是因为我忘记了我在操作系统或其他地方加速了鼠标滚轮在关于:配置

但是我正在处理一个类似的问题(顺便说一句,使用类似的应用程序,但不是画布),我只使用 +1 / -1 的增量符号和 随着时间的推移进行测量 最后一次发射时,你会有一个加速率,即。如果有人在几分钟内滚动一次 vs 几次(我敢打赌,谷歌地图就是这样做的)。

这个概念在我的测试中似乎很有效,只需将小于 100 毫秒的时间添加到加速中即可。

【讨论】:

【参考方案10】:
var onMouseWheel = function(e) 
    e = e.originalEvent;
    var delta = e.wheelDelta>0||e.detail<0?1:-1;
    alert(delta);

$("body").bind("mousewheel DOMMouseScroll", onMouseWheel);

【讨论】:

以上是关于跨浏览器规范鼠标滚轮速度的主要内容,如果未能解决你的问题,请参考以下文章

跨浏览器鼠标输入/离开解决方案

为啥谷歌浏览器,鼠标滚轮可以向下滚,但是不能向上滚,很不爽。

如何规范跨浏览器的 CSS3 过渡结束事件?

在跨浏览器测试期间同步用户输入

遇上浏览器跨域问题怎么办?

JS如何判断鼠标滚轮事件分析