在浏览器中规范鼠标滚轮速度

时间:2011-04-03 04:36:17

标签: javascript mousewheel

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

在该代码中,我使用鼠标滚轮放大/缩小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”值?

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

修改:进一步调查显示一个滚动事件“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,但滚动速度非常缓慢没有任何事情的HANDLER FIRES

所以问题是:

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

10 个答案:

答案 0 :(得分:50)

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

答案 1 :(得分:27)

这是我疯狂尝试产生跨浏览器相干和标准化增量(-1 <= delta&lt; = 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上运行良好

答案 2 :(得分:19)

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 :(得分:10)

我创建了一个表,其中包含不同事件/浏览器返回的不同值,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     |                |                |                |           |             |           |

答案 4 :(得分:6)

另一种或多或少自足的解决方案......

但事件之间不需要花费时间。有些浏览器似乎总是使用相同的delta来触发事件,并且在快速滚动时只需更快地触发它们。其他人确实改变了三角洲。人们可以想象一个需要花费时间的自适应规范化器,但这有点牵扯并且难以使用。

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

答案 5 :(得分:3)

对于触摸设备上的缩放支持,请注册gesturestart,gesturechange和gestureend事件,并使用event.scale属性。您可以看到example code

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

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

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

基本上在我自己的轮子事件测试中(我正在尝试规范化滚动值),我发现我得到的操作系统,浏览器供应商,浏览器版本,事件类型和设备(Microsoft转轮鼠标,笔记本电脑触摸板手势,笔记本电脑触摸板与scrollzone,苹果魔术鼠标,苹果强大鼠标滚动球,Mac触摸板等)。

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

答案 6 :(得分:2)

这是我今天和我一直争斗了几个小时的问题,而不是第一次:(

我一直试图通过“滑动”总结价值观,看看不同的浏览器如何报告价值,并且它们变化很大,几乎所有平台上的Safari报告数量级都更大,Chrome报告的数据更多(如比firefox好3倍,firefox在长期运行中保持平衡,但在小动作的平台上差异很大(在Ubuntu gnome上,几乎只有+3或-3,似乎它总结了较小的事件然后发送一个大的“+3” “)

目前发现的解决方案有三个:

  1. 已经提到的“仅使用符号”可以杀死任何类型的加速度
  2. 将浏览器嗅探到次要版本和平台,并正确调整
  3. Qooxdoo最近实施了一种自适应算法,它基本上试图根据目前收到的最小值和最大值来缩放增量。
  4. Qooxdoo的想法很好,并且有效,并且是我目前发现的唯一一个完全一致的跨浏览器解决方案。

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

    对于Mac用户(像我一样)而言,这对于在触摸板上进行有力的滚动滑动并期望到达滚动内容的顶部或底部是令人沮丧的。

    更重要的是,由于它根据获得的最大值缩小鼠标速度,用户尝试加速的速度越快,速度越慢,而“慢滚动”用户将体验到相当快的速度。

    这使得这个(非常出色的)解决方案更好地实现了解决方案1.

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

    如果你玩了一段时间,你会发现你会开始得到非常均匀的结果,但你也会注意到它的速度非常快+ 1 / -1。

    我正在努力增强它以更好地检测峰值,这样它们就不会发送“超出规模”的所有内容。同样可以获得0到1之间的浮点值作为delta值,这样就可以得到一个连贯的输出。

答案 7 :(得分:1)

在所有浏览器的所有操作系统中,绝对没有简单的方法来规范所有用户。

它比你列出的变种更糟糕 - 在我的WindowsXP + Firefox3.6设置上,我的鼠标滚轮每个单一滚动进行6次 - 可能是因为某个地方我忘记了我加速了鼠标滚轮,无论是在操作系统还是某个地方在about:config

然而,我正在研究一个类似的问题(使用类似的应用程序顺便说一句,但非画布),我只需使用+1 / -1的增量符号和随时间测量最后一次开火,你会有一个加速率,即。如果有人在短时间内多次滚动 (我敢打赌谷歌地图是如何做的)。

这个概念似乎在我的测试中运作良好,只需要将小于100毫秒的任何东西添加到加速度中。

答案 8 :(得分:0)

简单而有效的解决方案:

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

答案 9 :(得分:-1)

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