ブラウザー全体でのマウスホイール速度の正規化


147

以下のために別の質問私は構成この回答を含めて、このサンプルコード

このコードでは、マウスホイールを使用して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で同じ量のマウスホイールが回転するときに同じ「デルタ」値を取得するには、どのコードを使用できますか?

この質問は関連していますが、良い答えはありません。

編集:さらなる調査によると、1つのスクロールイベント「上」は次のとおりです。

                  | 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 | 未定義| -3
 Firefox v4 / OS X | 未定義| -1
Firefox v30 / OS X | 未定義| -1

さらに、OS XでMacBookトラックパッドを使用すると、動きが遅い場合でも異なる結果が得られます。

  • SafariとChromeでは、wheelDeltaマウスホイールの値は120ではなく3です。
  • Firefoxの場合detailは通常2、時々1ですが、非常にゆっくりとスクロールする場合、イベントハンドラーファイアはまったくありません。

だから問題は:

この動作を区別するための最良の方法は何ですか(理想的にはユーザーエージェントやOSスニッフィングなし)。


質問を削除しました。今、答えを書いています。さらに詳しく説明する前に、Mac OS XのSafariでのスクロールについて説明しますか?少しスクロールすると少しスクロールしますが、一定の速度を維持すると、徐々に速くなりますか?
Blender

@Blender OS Xでテスト中です。そうです、SafariはChromeの約20倍の速度でズームする外れ値です。残念ながら、私は物理的なマウスを接続していないので、私のテストは同等の距離と速度の2本の指のスワイプに制限されています。
Phrogz

OS XとWin7での上位5つのブラウザーの動作の詳細で質問を更新しました。それは地雷原であり、OS X上のChromeは問題のある異常値のようです。
Phrogz

@Phrogz e.wheelDelta/120でしょ?
サイムVIDAS

@ŠimeVidasはい、私がコピーして使用していたコードは明らかに間違っていました。以下の私の答えでより良いコードを見ることができます
Phrogz

回答:


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には2つの欠陥があります。OSX上の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からの提案の1つは、距離の符号を使用して調整することで、各イベント呼び出しを1回の移動として数えることです。これは、OS Xのスムーズなスクロール/加速スクロールでは優れた結果をもたらさず、マウスホイールが非常に速く移動した場合(例:wheelDelta240)、完全に処理されませんが、まれに発生します。ここで説明されている理由により、このコードは現在、この回答の上部に示されている推奨手法です。


私もオペラOS X上の1/3の違いを占めることを除いて、私が持っているものは基本的だŠimeVidasのおかげで、@
Phrogz

@ Phrogz、2014年9月にすべてのOS X / 3が追加された更新バージョンはありますか?これはコミュニティにとってすばらしい追加になるでしょう。
Basj

@Phrogz、これは素晴らしいでしょう。私はテストするMacを持っていません...(私は自分自身にあまり評判がなくても、そのための報奨金を喜んで提供します;))
Basj

1
Windows Firefox 35.0.1では、wheelDeltaは未定義であり、detailは常に0であるため、提供されたコードは失敗します。
Max Strater、2015

1
@MaxStrater同じ問題に直面しましたが、「deltaY」を追加(((evt.deltaY <0 || evt.wheelDelta>0) || evt.deltaY < 0) ? 1 : -1)して、QAが何を見つけるのかわからないような方向にこれを克服しました。
Brock

28

これが、クロスブラウザーのコヒーレントで正規化されたデルタ(-1 <=デルタ<= 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 2013年

完全な機能コードをデモ(例:jsFiddle)で入手できますか?
adardesign 2013

理由があるキャッシュeventに-objectはo
yckart 2013

いいえ、ありません。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 :)
ペリー

使い方の例を教えてください。私はそれを試してみましたが、FFで動作しますが、ChromeやIE(11)では動作しません。ありがとう
Andrew

2
npmを使用する人のために、Facebookの固定データテーブルから既に抽出されたこのコードだけのすぐに使用できるパッケージがあります。詳細については、ここを参照してくださいnpmjs.com/package/normalize-wheel
Simon Watson

11

一部のブラウザーが既にサポートしている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;
}

のイベントmousewheelwheelおよびDOMMouseScrollイベント:

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

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上のChromeではまったく機能しません。
justnorris 2013年

@Norris今はそうだと思います。この質問を見つけたところ、ここの例は私のMacbookでクロムで動作します
Harry Moreno

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のMozMousePixelScroll場合は、Gecko固有のイベントが役立つ可能性があります(ただし、DOMMouseWheelイベントがFirefoxで廃止されたため、おそらくこれは廃止されました)。

Windowsの場合、ドライバー自体がWM_MOUSEWHEEL、WM_MOUSEHWHEELイベント(およびおそらくタッチパッドのパン用のWM_GESTUREイベント?)を生成するようです。これは、Windowsまたはブラウザーがマウスホイールイベント値自体を正規化していないように見える理由を説明します(値を正規化するための信頼できるコードを記述できないことを意味する場合があります)。

以下のためにonwheelない onmousewheel)イベントInternet Explorerのサポート IE9とIE10のために、あなたも使用できるW3Cの標準 onwheelイベントを。ただし、1つのノッチは120とは異なる値になる可能性があります(たとえば、このテストページを使用したマウスでは、1つのノッチが(-120ではなく)111になります)。私は関連するかもしれない他の詳細ホイールイベントで別の記事を書きました。

基本的に、自分のホイールイベントのテスト(スクロールの値を正規化しようとしています)で、OS、ブラウザーベンダー、ブラウザーバージョン、イベントタイプ、およびデバイス(Microsoftチルトホイールマウス、ラップトップタッチパッドジェスチャー)にさまざまな値が表示されることがわかりました、スクロールゾーン付きラップトップタッチパッド、アップルマジックマウス、アップルマイティマウススクロールボール、Macタッチパッドなど)。

また、ブラウザー構成(Firefox mousewheel.enable_pixel_scrolling、chrome --scroll-pixels = 150など)、ドライバー設定(Synapticsタッチパッドなど)、OS構成(Windowsマウス設定、OSXマウス設定、 X.orgボタンの設定)。


2

これは私が今日数時間戦ってきた問題であり、初めてではありません:(

私は「スワイプ」で値を合計し、さまざまなブラウザーが値を報告する方法を確認しようとしています。それらは大きく変化します。Safariはほとんどすべてのプラットフォームで桁違いに大きな数値を報告し、Chromeはかなり多く(3倍以上)を報告します)firefoxよりも、Firefoxは長期的にバランスが取れていますが、小さな動きのプラットフォーム間でかなり異なります(Ubuntu gnomeでは、ほぼ+3または-3だけで、小さなイベントを合計して大きな "+3"を送信するように見えます)

現在見つかっている現在のソリューションは3つです。

  1. あらゆる種類の加速を殺す、すでに述べた「標識のみを使用」
  2. ブラウザをマイナーバージョンとプラットフォームまでスニッフィングし、適切に調整する
  3. Qooxdooは最近、基本的にこれまでに受信した最小値と最大値に基づいてデルタをスケーリングしようとする自己適応アルゴリズムを実装しました。

Qooxdooのアイデアは優れており、機能します。完全に一貫したクロスブラウザーであることが現在わかっている唯一のソリューションです。

残念ながらそれは加速もくりこむ傾向があります。(デモで)試して、しばらく最大速度で上下にスクロールすると、極端に速くまたは非常に遅くスクロールすると、基本的にほぼ同じ量の動きが生成されることがわかります。反対に、ページをリロードして非常にゆっくりとスワイプするだけの場合、かなり速くスクロールすることに気づくでしょう。」

これは、タッチパッドでスクロールスワイプを激しく行い、スクロールされたものの上部または下部に到達することを期待していたMacユーザー(私のような)にとってイライラします。

さらに、得られた最大値に基づいてマウスの速度が縮小されるため、ユーザーが速度を上げようとすればするほど、速度が遅くなり、「遅いスクロール」のユーザーは非常に速い速度を体験します。

これにより、この(そうでなければ素晴らしい)ソリューションは、ソリューション1の実装が少し良くなります。

ソリューションをjqueryマウスホイールプラグインに移植しました:http : //jsfiddle.net/SimoneGianni/pXzVv/

しばらく遊んでみると、かなり均一な結果が得られることがわかりますが、+ 1 / -1の値が非常に速くなる傾向があることにも気づくでしょう。

私は現在、ピークをより適切に検出するように拡張して、「スケール外」ですべてを送信しないように取り組んでいます。また、一貫した出力が得られるように、デルタ値として0〜1の浮動小数点値を取得することもできます。


1

すべてのブラウザのすべてのOSのすべてのユーザーを正規化する簡単な方法はありません。

それはあなたのリストされたバリエーションよりも悪くなります-私のWindowsXP + Firefox3.6セットアップでは、私のマウスホイールは1ノッチのスクロールにつき6つします-おそらくどこかで私がOSまたは約のどこかでマウスホイールを加速したためです:設定

ただし、私は同様の問題に取り組んでいます(同様のアプリですが、キャンバスではありません)。+ 1 / -1のデルタ記号を使用し、最後に発生した時間測定するだけで発生します。つまり、加速率があります。誰かがスクロールであれば、一度いくつかの瞬間で数回(私は賭けGoogleがそれをしないマッピングする方法であるでしょう)。

このコンセプトは私のテストではうまく機能しているようです。100ms未満の値を加速に追加するだけです。


-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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.