web-dev-qa-db-ja.com

firefoxでドラッグ中にinput type = rangeのonchangeイベントが発生しない

私が<input type="range">で遊んだとき、スライダーがドラッグされている間にChromeと他の人がonchangeイベントを引き起こす新しい位置にスライダーを落とす場合にのみFirefoxはonchangeイベントを引き起こします。

どのように私はそれをFirefoxでドラッグすることでそれを起こさせることができますか?

_ html _

<span id="valBox"></span>
<input type="range" min="5" max="10" step="1" onchange="showVal(this.value)">

_ script _

function showVal(newVal){
  document.getElementById("valBox").innerHTML=newVal;
}
217
Prasanth K C

どうやらChromeとSafariは間違っています:onchangeはユーザーがマウスを放したときにのみ引き起こされるべきです。継続的な更新を取得するには、oninputイベントを使用してください。このイベントは、Firefox、Safari、およびChromeで、マウスとキーボードの両方から最新の更新をキャプチャします。

ただし、oninputはIE10ではサポートされていないため、次のように2つのイベントハンドラを組み合わせることが最善の策です。

<span id="valBox"></span>
<input type="range" min="5" max="10" step="1" 
   oninput="showVal(this.value)" onchange="showVal(this.value)">

詳しくは Bugzillaスレッド をご覧ください。

396
Frederik

SUMMARY:

ここでは、現在のブラウザでは不可能な、範囲/スライダーの操作に一貫して応答する、jQueryを使用しないクロスブラウザデスクトップおよびモバイル機能を提供します。基本的に、すべてのブラウザーが、on("change"...イベントまたはon("change"...イベントのいずれかに対してIE11のon("input"...イベントをエミュレートするように強制します。新しい関数は...

function onRangeChange(r,f) {
  var n,c,m;
  r.addEventListener("input",function(e){n=1;c=e.target.value;if(c!=m)f(e);m=c;});
  r.addEventListener("change",function(e){if(!n)f(e);});
}

...ここでrは範囲入力要素であり、fはリスナーです。リスナーは、範囲/スライダーの値を変更する相互作用の後に呼び出されますが、その値を変更しない相互作用の後ではなく、現在のスライダー位置での初期マウスまたはタッチ相互作用、またはスライダーの両端からの移動時に呼び出されます。

問題:

2016年6月初旬の時点で、ブラウザは範囲/スライダーの使用に対する応答の点で異なります。 5つのシナリオが関連しています。

  1. 現在のスライダー位置での最初のマウスダウン(またはタッチスタート)
  2. 新しいスライダー位置での最初のマウスダウン(またはタッチスタート)
  3. スライダーに沿った1または2の後のマウス(またはタッチ)の以降の動き
  4. スライダーの両端を1または2過ぎた後のマウス(またはタッチ)の動き
  5. 最後のマウスアップ(またはタッチエンド)

次の表は、上記のシナリオのどれに対応するかに関して、少なくとも3つの異なるデスクトップブラウザーの動作がどのように異なるかを示しています。

table showing browser differences with respect to which events they respond to and when

解決策:

onRangeChange関数は、範囲/スライダーの相互作用に対する一貫した予測可能なクロスブラウザー応答を提供します。次の表に従って、すべてのブラウザーを強制的に動作させます。

table showing behaviour of all browsers using the proposed solution

IE11では、コードは基本的にすべてが現状どおりに動作することを許可します。つまり、"change"イベントが標準的な方法で機能することを可能にし、"input"イベントはとにかく起動しないので無関係です。他のブラウザでは、"change"イベントは効果的に無音になります(余分な、時には準備ができていないように見えるイベントが発生しないようにします)。さらに、"input"イベントは、範囲/スライダーの値が変更されたときにのみリスナーを起動します。一部のブラウザ(Firefoxなど)では、上記のリストのシナリオ1、4、5でリスナーが事実上無音になっているために発生します。

(シナリオ1、4、5のいずれかでリスナーを本当にアクティブにする必要がある場合は、"mousedown"/"touchstart""mousemove"/"touchmove"、および/または"mouseup"/"touchend"イベントを組み込むことができます。この回答の範囲を超えています。)

モバイルブラウザの機能:

このコードはデスクトップブラウザーでテストしましたが、モバイルブラウザーではテストしていません。ただし、 このページの別の回答 MBourneは、ここで私のソリューションを示した」......見つけることができるすべてのブラウザーで動作するように見える(Winデスクトップ:IE、Chrome、 Opera、FF; Android Chrome、OperaおよびFF、iOS Safari)」。 (MBourneに感謝します。)

使用法:

このソリューションを使用するには、上記の概要(簡略化/縮小化)のonRangeChange関数または以下のデモコードスニペット(機能的には同じですが、よりわかりやすい)を独自のコードに含めます。次のように呼び出します。

onRangeChange(myRangeInputElmt, myListener);

ここで、myRangeInputElmtは目的の<input type="range"> DOM要素であり、myListener"change"のようなイベントで呼び出すリスナー/ハンドラー関数です。

リスナーは、必要に応じてパラメーターなしにすることも、eventパラメーターを使用することもできます。つまり、ニーズに応じて、次のいずれかが機能します。

var myListener = function() {...

または

var myListener = function(evt) {...

input要素からのイベントリスナーの削除(たとえば、removeEventListenerの使用)は、この回答では扱われていません。)

デモの説明:

以下のコードスニペットでは、関数onRangeChangeがユニバーサルソリューションを提供します。残りのコードは、その使用方法を示すための単なる例です。 my...で始まる変数はユニバーサルソリューションとは無関係であり、デモのためにのみ存在します。

デモでは、範囲/スライダーの値と、標準の"change""input"、およびカスタム"onRangeChange"イベントが発生した回数(それぞれ行A、B、C)を示します。このスニペットを異なるブラウザーで実行する場合、範囲/スライダーを操作する際には次の点に注意してください。

  • IE11では、行AとCの値は両方とも上記のシナリオ2と3で変化しますが、行Bは決して変化しません。
  • ChromeとSafariでは、行BとCの値は両方ともシナリオ2と3で変化しますが、行Aはシナリオ5でのみ変化します。
  • Firefoxでは、行Aの値はシナリオ5でのみ変更され、行Bは5つのシナリオすべてで変更され、行Cはシナリオ2および3でのみ変更されます。
  • 上記のすべてのブラウザーで、行C(提案されたソリューション)の変更は同じです。つまり、シナリオ2と3のみです。

デモコード:

// main function for emulating IE11's "change" event:

function onRangeChange(rangeInputElmt, listener) {

  var inputEvtHasNeverFired = true;

  var rangeValue = {current: undefined, mostRecent: undefined};
  
  rangeInputElmt.addEventListener("input", function(evt) {
    inputEvtHasNeverFired = false;
    rangeValue.current = evt.target.value;
    if (rangeValue.current !== rangeValue.mostRecent) {
      listener(evt);
    }
    rangeValue.mostRecent = rangeValue.current;
  });

  rangeInputElmt.addEventListener("change", function(evt) {
    if (inputEvtHasNeverFired) {
      listener(evt);
    }
  }); 

}

// example usage:

var myRangeInputElmt = document.querySelector("input"          );
var myRangeValPar    = document.querySelector("#rangeValPar"   );
var myNumChgEvtsCell = document.querySelector("#numChgEvtsCell");
var myNumInpEvtsCell = document.querySelector("#numInpEvtsCell");
var myNumCusEvtsCell = document.querySelector("#numCusEvtsCell");

var myNumEvts = {input: 0, change: 0, custom: 0};

var myUpdate = function() {
  myNumChgEvtsCell.innerHTML = myNumEvts["change"];
  myNumInpEvtsCell.innerHTML = myNumEvts["input" ];
  myNumCusEvtsCell.innerHTML = myNumEvts["custom"];
};

["input", "change"].forEach(function(myEvtType) {
  myRangeInputElmt.addEventListener(myEvtType,  function() {
    myNumEvts[myEvtType] += 1;
    myUpdate();
  });
});

var myListener = function(myEvt) {
  myNumEvts["custom"] += 1;
  myRangeValPar.innerHTML = "range value: " + myEvt.target.value;
  myUpdate();
};

onRangeChange(myRangeInputElmt, myListener);
table {
  border-collapse: collapse;  
}
th, td {
  text-align: left;
  border: solid black 1px;
  padding: 5px 15px;
}
<input type="range"/>
<p id="rangeValPar">range value: 50</p>
<table>
  <tr><th>row</th><th>event type                     </th><th>number of events    </th><tr>
  <tr><td>A</td><td>standard "change" events         </td><td id="numChgEvtsCell">0</td></tr>
  <tr><td>B</td><td>standard "input" events          </td><td id="numInpEvtsCell">0</td></tr>
  <tr><td>C</td><td>new custom "onRangeChange" events</td><td id="numCusEvtsCell">0</td></tr>
</table>

クレジット:

ここでの実装は主に私自身のものですが、 MBourneの答え に触発されました。他の回答は、「入力」イベントと「変更」イベントをマージでき、結果のコードがデスクトップとモバイルの両方のブラウザーで機能することを示唆しています。ただし、その回答のコードでは、隠された「余分な」イベントが発生しますが、それ自体が問題であり、発生するイベントはブラウザ間で異なるという問題があります。ここでの実装は、これらの問題を解決します。

キーワード:

JavaScript入力タイプの範囲スライダーイベントは、入力ブラウザーの互換性を変更しますクロスブラウザーデスクトップモバイルno-jQuery

32
Andrew Willems

UPDATE:私はここでこの答えをデスクトップ(しかしモバイルではない)ブラウザでマウスイベントを使って範囲/スライダーの相互作用を使う方法の例として残しています。しかし、私は/ /このページの他の場所にもクロスブラウザデスクトップを提供するための異なるアプローチを使用した まったく違う、そしてより良い答え を書いています-and-mobile問題。

元の答え:

概要:ブラウザ間で矛盾して動作するon('input'...on('change'...を使用せずに範囲入力値を読み取ることができる、クロスブラウザのプレーンJavaScript(つまり、jQueryなし)ソリューション。

今日(2016年2月下旬)現在、ブラウザの不一致がまだあるので、ここで新しい回避策を提供しています。

問題:範囲入力、つまりスライダーを使用するとき、on('input'...はMacとWindows Firefox、ChromeとOperaとMac Safariで絶えず更新された範囲値を提供しますが、on('change'...はマウスアップ時に範囲値を報告するだけです。対照的に、Internet Explorer(v11)では、on('input'...はまったく機能せず、on('change'...は継続的に更新されます。

ここでは、mousedown、mousemove、および(おそらく)mouseupイベントを使用して、Vanilla JavaScript(つまりjQueryを使用しない)を使用してすべてのブラウザで同一の連続範囲値レポートを取得する2つの方法を報告します。

戦略1:短くても効率が悪い

より効率的なコードよりも短いコードを好む場合は、mousedownとmousemoveを使用し、mouseupを使用しない、この最初のソリューションを使用できます。これは必要に応じてスライダーを読み込みますが、ユーザーがクリックしていないためスライダーをドラッグしていない場合でも、マウスオーバーイベントの間は不必要に発砲し続けます。それは本質的に 'mousedown'イベントの後と 'mousemove'イベントの間の両方で範囲の値を読みます、requestAnimationFrameを使ってそれぞれをわずかに遅らせます。

var rng = document.querySelector("input");

read("mousedown");
read("mousemove");
read("keydown"); // include this to also allow keyboard control

function read(evtType) {
  rng.addEventListener(evtType, function() {
    window.requestAnimationFrame(function () {
      document.querySelector("div").innerHTML = rng.value;
      rng.setAttribute("aria-valuenow", rng.value); // include for accessibility
    });
  });
}
<div>50</div><input type="range"/>

戦略2:より長く、より効率的

より効率的なコードが必要で、より長いコード長を許容できる場合は、mousedown、mousemove、およびmouseupを使用する次の解決策を使用できます。これも必要に応じてスライダーを読み取りますが、マウスボタンが離されるとすぐにスライダーの読み取りを適切に停止します。本質的な違いは、「mousedown」の後に「mousemove」の待機を開始するだけで、「mouseup」の後に「mousemove」の待機を停止することです。

var rng = document.querySelector("input");

var listener = function() {
  window.requestAnimationFrame(function() {
    document.querySelector("div").innerHTML = rng.value;
  });
};

rng.addEventListener("mousedown", function() {
  listener();
  rng.addEventListener("mousemove", listener);
});
rng.addEventListener("mouseup", function() {
  rng.removeEventListener("mousemove", listener);
});

// include the following line to maintain accessibility
// by allowing the listener to also be fired for
// appropriate keyboard events
rng.addEventListener("keydown", listener);
<div>50</div><input type="range"/>

Demo:上記の回避策の必要性と実装に関するより完全な説明

次のコードは、この戦略のさまざまな側面をさらに詳しく示しています。説明はデモに埋め込まれています。

var select, inp, listen, unlisten, anim, show, onInp, onChg, onDn1, onDn2, onMv1, onMv2, onUp, onMvCombo1, onDnCombo1, onUpCombo2, onMvCombo2, onDnCombo2;

select   = function(selctr)     { return document.querySelector(selctr);      };
inp = select("input");
listen   = function(evtTyp, cb) { return inp.   addEventListener(evtTyp, cb); };
unlisten = function(evtTyp, cb) { return inp.removeEventListener(evtTyp, cb); };
anim     = function(cb)         { return window.requestAnimationFrame(cb);    };
show = function(id) {
        return function() {
    select("#" + id + " td~td~td"   ).innerHTML = inp.value;
    select("#" + id + " td~td~td~td").innerHTML = (Math.random() * 1e20).toString(36); // random text
  };
};

onInp      =                  show("inp" )                                      ;
onChg      =                  show("chg" )                                      ;
onDn1      =                  show("mdn1")                                      ;
onDn2      = function() {anim(show("mdn2"));                                   };
onMv1      =                  show("mmv1")                                      ;
onMv2      = function() {anim(show("mmv2"));                                   };
onUp       =                  show("mup" )                                      ;
onMvCombo1 = function() {anim(show("cmb1"));                                   };
onDnCombo1 = function() {anim(show("cmb1"));   listen("mousemove", onMvCombo1);};
onUpCombo2 = function() {                    unlisten("mousemove", onMvCombo2);};
onMvCombo2 = function() {anim(show("cmb2"));                                   };
onDnCombo2 = function() {anim(show("cmb2"));   listen("mousemove", onMvCombo2);};

listen("input"    , onInp     );
listen("change"   , onChg     );
listen("mousedown", onDn1     );
listen("mousedown", onDn2     );
listen("mousemove", onMv1     );
listen("mousemove", onMv2     );
listen("mouseup"  , onUp      );
listen("mousedown", onDnCombo1);
listen("mousedown", onDnCombo2);
listen("mouseup"  , onUpCombo2);
table {border-collapse: collapse; font: 10pt Courier;}
th, td {border: solid black 1px; padding: 0 0.5em;}
input {margin: 2em;}
li {padding-bottom: 1em;}
<p>Click on 'Full page' to see the demonstration properly.</p>
<table>
  <tr><th></th><th>event</th><th>range value</th><th>random update indicator</th></tr>
  <tr id="inp" ><td>A</td><td>input                                </td><td>100</td><td>-</td></tr>
  <tr id="chg" ><td>B</td><td>change                               </td><td>100</td><td>-</td></tr>
  <tr id="mdn1"><td>C</td><td>mousedown                            </td><td>100</td><td>-</td></tr>
  <tr id="mdn2"><td>D</td><td>mousedown using requestAnimationFrame</td><td>100</td><td>-</td></tr>
  <tr id="mmv1"><td>E</td><td>mousemove                            </td><td>100</td><td>-</td></tr>
  <tr id="mmv2"><td>F</td><td>mousemove using requestAnimationFrame</td><td>100</td><td>-</td></tr>
  <tr id="mup" ><td>G</td><td>mouseup                              </td><td>100</td><td>-</td></tr>
  <tr id="cmb1"><td>H</td><td>mousedown/move combo                 </td><td>100</td><td>-</td></tr>
  <tr id="cmb2"><td>I</td><td>mousedown/move/up combo              </td><td>100</td><td>-</td></tr>
</table>
<input type="range" min="100" max="999" value="100"/>
<ol>
  <li>The 'range value' column shows the value of the 'value' attribute of the range-type input, i.e. the slider. The 'random update indicator' column shows random text as an indicator of whether events are being actively fired and handled.</li>
  <li>To see browser differences between input and change event implementations, use the slider in different browsers and compare A and&nbsp;B.</li>
  <li>To see the importance of 'requestAnimationFrame' on 'mousedown', click a new location on the slider and compare C&nbsp;(incorrect) and D&nbsp;(correct).</li>
  <li>To see the importance of 'requestAnimationFrame' on 'mousemove', click and drag but do not release the slider, and compare E&nbsp;(often 1&nbsp;pixel behind) and F&nbsp;(correct).</li>
  <li>To see why an initial mousedown is required (i.e. to see why mousemove alone is insufficient), click and hold but do not drag the slider and compare E&nbsp;(incorrect), F&nbsp;(incorrect) and H&nbsp;(correct).</li>
  <li>To see how the mouse event combinations can provide a work-around for continuous update of a range-type input, use the slider in any manner and note whichever of A or B continuously updates the range value in your current browser. Then, while still using the slider, note that H and I provide the same continuously updated range value readings as A or B.</li>
  <li>To see how the mouseup event reduces unnecessary calculations in the work-around, use the slider in any manner and compare H and&nbsp;I. They both provide correct range value readings. However, then ensure the mouse is released (i.e. not clicked) and move it over the slider without clicking and notice the ongoing updates in the third table column for H but not&nbsp;I.</li>
</ol>
29
Andrew Willems

これは回答として投稿していますが、それはあまり有用でない回答の下でのコメントではなく、それ自身の回答であるに値します。私はこの方法がHTMLとは別のファイルにすべてのjsを保持できるので、受け入れられた答えよりもはるかに優れていると思います。

受け入れられた答えの下で彼のコメントのJamrelianによって提供された答え。

$("#myelement").on("input change", function() {
    //do something
});

Jaimeによるこのコメントにだけ注意してください

このソリューションでは、chromeではハンドラへの呼び出しが2回(イベントごとに1回)行われるため、それを気にする場合はそれを防ぐ必要があります。

あなたがマウスを動かすのを止めたとき、そしてあなたがマウスボタンを放したときにそれはそれとしてイベントを発生させるでしょう。

26
Daniel Tonon

Andrew Willemのソリューションは、モバイルデバイスと互換性がありません。

これが、Edge、IE、Opera、FF、Chrome、iOS Safari、それにモバイル版(私がテストできる)で機能する、彼の2番目のソリューションの修正です。

更新1: "requestAnimationFrame"の部分を削除しました。必要ではないと同意します。

var listener = function() {
  // do whatever
};

slider1.addEventListener("input", function() {
  listener();
  slider1.addEventListener("change", listener);
});
slider1.addEventListener("change", function() {
  listener();
  slider1.removeEventListener("input", listener);
}); 

更新2:Andrewの2016年6月2日に更新された答えへの応答:

Andrew - これは私が見つけることができたすべてのブラウザ(Winデスクトップ:IE、Chrome、Opera、FF、Android Chrome、Opera、FF、iOS Safari)で動作するようです。

更新3:if( "oninput in slider")解決策

以下は上記のすべてのブラウザで動作するようです。私はこれを使用していましたが、その後IEで失敗したので、別のものを探しに行ったので、ここで終わりました。

if ("oninput" in slider1) {
    slider1.addEventListener("input", function () {
        // do whatever;
    }, false);
}

しかし、私があなたの解決策をチェックする前に、これがIEで再び働いていたことに気づきました - おそらく他の何らかの衝突がありました。

4
MBourne

クロスブラウザの動作をよくし、わかりやすいコードを得るには、onchange属性をフォームと組み合わせて使用​​するのが最善です。

function showVal(){
  valBox.innerHTML = inVal.value;

}
<form onchange="showVal()">
  <input type="range" min="5" max="10" step="1" id="inVal">
  </form>

<span id="valBox">
  </span>

oninput を使用しても、値は直接変更されます。

function showVal(){
  valBox.innerHTML = inVal.value;

}
<form oninput="showVal()">
  <input type="range" min="5" max="10" step="1" id="inVal">
  </form>

<span id="valBox">
  </span>
2
Cryptopat

さらに別のアプローチ - どのタイプのイベントを処理すべきかをシグナリングする要素にフラグを設定するだけです。

function setRangeValueChangeHandler(rangeElement, handler) {
    rangeElement.oninput = (event) => {
        handler(event);
        // Save flag that we are using onInput in current browser
        event.target.onInputHasBeenCalled = true;
    };

    rangeElement.onchange = (event) => {
        // Call only if we are not using onInput in current browser
        if (!event.target.onInputHasBeenCalled) {
            handler(event);
        }
    };
}
0
Nikita