web-dev-qa-db-ja.com

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

別の質問 の場合 この回答 を構成し、 このサンプルコード を含みます。

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

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

編集:さらに調査すると、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 | undefined 
 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ですが、非常にゆっくりスクロールする場合はイベントハンドラーなしAT ALLです。

質問は次のとおりです。

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

135
Phrogz

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

Firefoxの動作とOS XのChromeを検出および改善するための提案を歓迎します。

Edit:@Tomからの提案の1つは、各イベント呼び出しを単一の動きとして単純にカウントすることです。距離の符号を使用して調整します。これは、OS Xでのスムーズ/加速スクロールでは優れた結果をもたらさず、マウスホイールが非常に高速で移動する場合(例:wheelDeltaは240)を完全に処理しませんが、これらはまれにしか発生しません。このコードは、ここで説明されている理由により、この回答の上部に示されている推奨テクニックです。

53
Phrogz

クロスブラウザのコヒーレントで正規化されたデルタ(-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)およびIE 7ではXPで非常に良好に機能します。

28
smrtl

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

20
George

さまざまなイベント/ブラウザから返されるさまざまな値でテーブルを作成しました。 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     |                |                |                |           |             |           |
10
Sergio

別の多かれ少なかれ自己完結型のソリューション...

ただし、これはイベント間の時間を考慮しません。一部のブラウザは、常に同じデルタでイベントを発生させ、高速でスクロールするときはより速くそれらを発生させるようです。他のものはデルタを変化させます。時間を考慮に入れる適応ノーマライザーを想像できますが、それはやや複雑で使いにくいものになります。

ここで利用可能な作業: 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(); }
}
6
Marijn

タッチデバイスでズームをサポートするには、gesturestart、gesturechange、gestureendイベントに登録し、event.scaleプロパティを使用します。 サンプルコード が表示されます。

Firefox 17の場合、onwheelイベントはデスクトップおよびモバイルバージョンでサポートされる予定です( MDN docs onwheel )。また、Firefoxの場合、Gecko固有のMozMousePixelScrollイベントが役立つ場合があります(DOMMouseWheelイベントがFirefoxで非推奨になったため、おそらくこれは非推奨になりました)。

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

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

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

また、ブラウザ設定(Firefox mousewheel.enable_pixel_scrolling、chrome --scroll-pixels = 150など)、ドライバー設定(Synapticsタッチパッドなど)、OS設定(Windowsマウスなど)によるさまざまな副作用を無視する必要があります。設定、OSXマウス設定、X.orgボタン設定)。

3
robocat

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

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

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

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

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

残念ながら、加速も繰り込みます。 (デモで)試してみて、しばらくの間最大速度で上下にスクロールすると、非常に高速または非常に低速でスクロールすると、基本的にほぼ同じ量の動きが発生することがわかります。反対に、ページをリロードして非常にゆっくりとスワイプするだけで、非常に速くスクロールすることに気付くでしょう」。

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

さらに、取得した最大値に基づいてマウスの速度を縮小するため、ユーザーが高速化しようとすると速度が低下しますが、「低速スクロール」ユーザーは非常に高速になります。

これにより、この(そうでなければ素晴らしい)ソリューションがソリューション1のわずかに優れた実装になります。

Jquery mousewheelプラグインにソリューションを移植しました: http://jsfiddle.net/SimoneGianni/pXzVv/

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

現在、ピークをより正確に検出できるように機能強化を行っており、「スケール外」ですべてが送信されることはありません。また、コヒーレントな出力が得られるように、0から1の間の浮動小数点値をデルタ値として取得することもできます。

2
Simone Gianni

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

リストされたバリエーションよりも悪化します-WindowsXP + Firefox3.6のセットアップでは、マウスホイールは1ノッチスクロールごとに6回動作します。 config

しかし、私は同様の問題に取り組んでおり(同様のアプリで、キャンバスではありません)、+ 1/-1のデルタ記号と時間をかけて測定する最後の発射された時点で、加速率が得られます。誰かがスクロールする場合1回 vs 数秒間に数回(Googleマップが行う方法は間違いないでしょう)。

この概念は私のテストではうまく機能しているようで、100ミリ秒未満を加速に追加するだけです。

1
ck_

シンプルで実用的なソリューション:

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