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

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

在该代码中,我使用鼠标滚轮来放大/缩小HTML5canvas。 我发现一些代码可以规范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 |  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(可能是错的)
  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 |  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 ,但是当滚动速度非常慢时, 没有事件处理程序

所以问题是:

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

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

你可以在你自己的浏览器上testing这个代码: http : //phrogz.net/JS/wheeldelta.html

欢迎在OS X上检测和改进Firefox和Chrome上的行为的build议。

编辑 :从@Tom的一个build议是简单地计数每个事件调用作为一个单一的举动,使用距离的符号来调整它。 这在OS X平滑/加速滚动下不会给出很好的结果,也不能在鼠标滚轮移动得非常快的情况下处理完美的情况(例如wheelDelta是240),但这种情况很less发生。 这个代码现在是在这个答案的顶部显示的推荐技术,出于在那里描述的原因。

这是我疯狂的尝试,以产生一个跨浏览器相干和规范化三angular洲(-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上效果相当不错

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

我已经在使用React构build的数据表上进行了testing,它像黄油一样滚动!

此解决scheme适用于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 }; } 

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

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

基于此,我使这个function正常化的速度:

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

另一个或多或less的独立解决scheme…

不过,事件之间并不需要时间。 一些浏览器似乎总是以相同的三angular洲发射事件,并且在快速滚动时更快地发射它们。 其他人确实改变了三angular洲。 人们可以想象一个需要时间考虑的自适应归一化器,但是这会使用起来有些牵扯和尴尬。

在这里工作: 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事件是有用的(尽pipe现在已经废弃了,因为DOMMouseWheel事件现在在Firefox中已经被废弃了)。

对于Windows,驱动程序本身似乎生成WM_MOUSEWHEEL,WM_MOUSEHWHEEL事件(也许触摸板平移的WM_GESTURE事件?)。 这将解释为什么Windows或浏览器似乎没有正常化mousewheel事件值本身(也许意味着你不能编写可靠的代码来规范化值)。

IE9和IE10的IE 浏览器支持IE浏览器的 onwheel不是 onmousewheel)事件,还可以使用W3C标准的 onwheel事件。 然而,一个凹口可以是一个不同于120的值(例如, 使用这个testing页面 ,我鼠标上的一个凹口变成了111(而不是-120))。 我写了另一篇文章与其他细节轮相关的事件。

基本上在我自己的车轮事件testing(我正在试图规范滚动的值),我发现我得到不同的值的操作系统,浏览器供应商,浏览器版本,事件types和设备(微软tiltwheel鼠标,笔记本触摸板手势,带滚动区的笔记本电脑触摸板,苹果魔术鼠标,苹果强大的鼠标滚动球,Mac触摸板等)。

并且必须忽略来自浏览器configuration(例如,Firefox mousewheel.enable_pixel_scrolling,chrome -scroll-pixels = 150),驱动程序设置(例如Synaptics触摸板)和OSconfiguration(Windows鼠标设置,OSX鼠标首选项, X.orgbutton设置)。

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

我一直试图总结一个“滑动”的价值观,看看不同的浏览器如何报告价值,他们差异很大,几乎在所有平台上的Safari报告顺序数量更大,Chrome报告更多(如3倍以上)比firefox,firefox是长期平衡的,但在小型移动平台(Ubuntu的GNOME,几乎只有+3或-3,似乎总结小事件,然后发送一个大的“+3”)大不相同

目前发现的解决scheme有三种:

  1. 已经提到的“只用标志”杀死任何一种加速度
  2. 浏览器上的小版本和平台,并适当调整
  3. Qooxdoo最近实现了一个自适应algorithm,它基本上试图根据迄今为止收到的最小值和最大值来缩放增量。

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

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

这对于一个Mac用户(比如我)曾经在触摸板上进行有力的滚动滑动并期望进入滚动事物的顶部或底部而言是令人沮丧的。

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

这使得这个(否则辉煌的)解决scheme稍微好一些的解决scheme1的实施。

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

如果你玩了一段时间,你会发现你会得到相当一致的结果,但是你也会注意到它往往会相当快地加1 / -1的值。

我现在正在努力增强它,以便更好地检测峰值,以便它们不会发送“超出规模”的所有内容。 也可以获得0和1之间的浮点值作为增量值,这样就可以获得相干输出。

所有浏览器中的所有操作系统中的所有用户都没有简单的标准化方法。

它比你列出的变化更糟糕 – 在我的WindowsXP + Firefox3.6设置我的鼠标滚轮每个凹槽滚动6 – 可能是因为我忘记了我的地方加快了鼠标滚轮,无论是在操作系统或在某处:configuration

然而,我正在处理类似的问题(类似的应用程序顺便说一句,但非帆布),它发生在我身上,只使用+1 / -1的三angular洲标志和测量时间上次开火,你会有一个加速的速度,即。 如果有人在几分钟内滚动一次 (我敢打赌,谷歌地图是如何做到的)。

这个概念似乎在我的testing中performance得很好,只需要less于100ms就能加速加速。

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