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

IT技术 javascript mousewheel
2021-01-14 22:31:37

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

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

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

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

                  | 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
 火狐 v4/Win7 | 未定义 | -3
 火狐 v4/OS X | 未定义 | -1
火狐 v30/OS X | 未定义 | -1

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

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

所以问题是:

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

6个回答

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

@Phrogz,您在 2014 年 9 月是否有添加所有 OS X / 3 的更新版本?这对社区来说将是一个很好的补充!
2021-03-15 22:31:37
@Phrogz,这会很棒。我这里没有 Mac 可以测试......(我很乐意为此提供赏金,即使我自己没有多少声誉;))
2021-03-19 22:31:37
在 Windows Firefox 35.0.1 上,wheelDelta 未定义且 detail 始终为 0,这会导致提供的代码失败。
2021-03-31 22:31:37
@ŠimeVidas 谢谢,这基本上就是我所拥有的,除了我还考虑了 Opera OS X 上的 1/3 差异。
2021-04-09 22:31:37
@MaxStrater 面临同样的问题,我已经添加了“deltaY”来克服这个方向,(((evt.deltaY <0 || evt.wheelDelta>0) || evt.deltaY < 0) ? 1 : -1)但不确定 QA 发现了什么。
2021-04-10 22:31:37

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

这东西太棒了。刚刚使用它,就像一个魅力!干得好脸书:)
2021-03-16 22:31:37
我将其用于缩放控制,但对我而言,使用 Macbook 触控板和使用我的 Logitech MX Anywhere 2 鼠标与此代码之间存在巨大差异。
2021-03-19 22:31:37
一个更直接的链接,没有绑定到 normalizeWHeel.js 的原始代码github.com/facebook/fixed-data-table/blob/master/src/...
2021-03-26 22:31:37
感谢@RobinLuiten,更新原始帖子。
2021-04-05 22:31:37
对于任何使用 npm 的人来说,都可以直接使用这个代码包,这些代码已经从 Facebook 的固定数据表中提取出来。有关更多详细信息,请参阅此处npmjs.com/package/normalize-wheel
2021-04-10 22:31:37

这是我疯狂尝试生成跨浏览器一致且标准化的 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) 和 XP 上的 IE 7 上效果很好

我认为你也应该fvar.
2021-03-19 22:31:37
不,没有。o变量用于显示我们想要原始事件,而不是像 jQuery 或其他库那样的包装事件可能传递给事件处理程序。
2021-03-24 22:31:37
是否有理由-object缓存evento
2021-03-26 22:31:37
如果我可以再投票 10 次,我可以。太感谢了!
2021-03-28 22:31:37
您能否在演示(例如 jsFiddle)中提供完整的功能代码?
2021-04-03 22:31:37

考虑到 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;
}

mousewheel,wheelDOMMouseScroll事件的

| 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     |                |                |                |           |             |           |
导致当前 Safari 和 Firefox 在 macOS 下滚动速度不同。
2021-03-16 22:31:37

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

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

可在此处工作: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(); }
}
@Norris 我相信现在可以了。刚刚发现这个问题,这里的例子适用于我的 macbook 和 chrome
2021-03-16 22:31:37
此解决方案根本不适用于 Mac 上的带触控板的 Chrome。
2021-03-25 22:31:37