输入类型=范围的onchange事件在拖动时不会在firefox中触发

时间:2013-08-31 05:35:44

标签: javascript html5 firefox input onchange

当我使用<input type="range">时,只有当我们将滑块拖放到Chrome和其他人在拖动滑块时触发更改事件的新位置时,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;
}

9 个答案:

答案 0 :(得分:396)

显然Chrome和Safari错误:onchange只应在用户释放鼠标时触发。要获得持续更新,您应该使用oninput事件,该事件将通过鼠标和键盘捕获Firefox,Safari和Chrome中的实时更新。

但是,IE10不支持oninput,所以最好的办法是组合两个事件处理程序,如下所示:

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

有关详细信息,请查看此Bugzilla thread

答案 1 :(得分:30)

<强>概要

我在这里提供了一种无jQuery跨浏览器的桌面和移动功能,可以持续响应范围/滑块交互,这在当前浏览器中是不可能的。它实质上强制所有浏览器模拟IE11的on("change"...on("change"...事件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月初,不同的浏览器在响应范围/滑块使用方面有所不同。有五种情况是相关的:

  1. 当前滑块位置的初始鼠标按下(或触摸开始)
  2. 在新滑块位置初始鼠标按下(或触摸开始)
  3. 沿滑块1或2后的任何后续鼠标(或触摸)移动
  4. 滑块任意一端后1或2之后的任何后续鼠标(或触摸)移动
  5. 最终鼠标(或触摸结束)
  6. 下表显示了至少三种不同的桌面浏览器在行为方面与上述哪些场景的响应不同:

    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" / {{ 1}}和/或"touchmove" / "mouseup"事件。此解决方案超出了此答案的范围。)

    移动浏览器中的功能

    我已在桌面浏览器中测试过此代码,但未在任何移动浏览器中测试过。但是,在another answer on this page MBourne已经证明我的解决方案&#34; ...似乎适用于我能找到的每个浏览器(Win桌面:IE,Chrome,Opera,FF; Android Chrome,Opera和FF,iOS Safari)&#34; 。 (感谢MBourne。)

    <强>用法:

    要使用此解决方案,请在您自己的代码中包含上面摘要中的"touchend"函数(简化/缩小)或下面的演示代码段(功能相同但更加不言自明)。按如下方式调用它:

    onRangeChange

    其中onRangeChange(myRangeInputElmt, myListener); 是您想要的myRangeInputElmt DOM元素,而<input type="range">是您希望在myListener上调用的侦听器/处理函数 - 类似事件。

    如果需要,您的监听器可以不含参数,也可以使用"change"参数,即根据您的需要,以下任何一种都可以使用:

    event

    var myListener = function() {...
    

    (从var myListener = function(evt) {... 元素中删除事件监听器(例如,使用input)在此答案中未解决。)

    演示说明:

    在下面的代码段中,函数removeEventListener提供了通用解决方案。其余的代码只是一个展示其用途的例子。任何以onRangeChange开头的变量都与通用解决方案无关,只是为了演示而存在。

    该演示显示范围/滑块值以及标准my..."change"和自定义"input"事件触发的次数(分别为行A,B和C) 。在不同浏览器中运行此代码段时,请在与范围/滑块进行交互时注意以下事项:

    • 在IE11中,行A和C中的值都在上面的场景2和3中发生变化,而行B从未发生变化。
    • 在Chrome和Safari中,行B和C中的值都在方案2和3中更改,而行A仅在方案5中更改。
    • 在Firefox中,行A中的值仅针对方案5进行更改,针对所有五种方案的行B更改进行更改,而行C仅针对方案2和3进行更改。
    • 在上述所有浏览器中,行C(建议的解决方案)中的更改是相同的,即仅适用于方案2和3。

    演示代码:

    &#13;
    &#13;
    "onRangeChange"
    &#13;
    // 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);
    &#13;
    table {
      border-collapse: collapse;  
    }
    th, td {
      text-align: left;
      border: solid black 1px;
      padding: 5px 15px;
    }
    &#13;
    &#13;
    &#13;

    <强>信用卡:

    虽然这里的实施主要是我自己的,但它的灵感来自于MBourne's answer。其他答案表明&#34;输入&#34;和&#34;改变&#34;事件可以合并,结果代码可以在桌面和移动浏览器中使用。但是,该答案中的代码会导致隐藏&#34;额外&#34;被触发的事件本身就有问题,并且触发的事件在浏览器之间有所不同,这是另一个问题。我在这里的实现解决了这些问题。

    <强>关键词:

    JavaScript输入类型范围滑块事件更改输入浏览器兼容性跨浏览器桌面移动no-jQuery

答案 2 :(得分:29)

更新:我在这里留下这个答案作为如何使用鼠标事件在桌面(但不是移动)浏览器中使用范围/滑块交互的示例。但是,我现在还在此页面的其他位置写了a completely different and, I believe, better answer,它使用不同的方法为此问题提供跨浏览器桌面 - 和 - 移动解决方案。

原始回答:

摘要:一种跨浏览器的纯JavaScript(即无jQuery)解决方案,允许读取范围输入值,而不使用on('input'...和/或on('change'...,它们在浏览器之间不一致。< /强>

截至今天(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)在所有浏览器中获得相同的连续范围值报告。

策略1:更短但效率更低

如果您希望使用更短的代码而不是更高效的代码,则可以使用第一个使用mousesdown和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"/>

演示:对上述解决方案的必要性和实施的更全面的解释

以下代码更全面地展示了此策略的众多方面。说明中嵌入了解释:

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>

答案 3 :(得分:26)

我发布这个作为答案,因为它应该是它自己的答案,而不是在一个不太有用的答案下的评论。我发现这种方法比接受的答案要好得多,因为它可以将所有js保存在与HTML不同的文件中。

Jamrelian在接受回答的评论中提供的答案。

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

请注意Jaime的评论

  

请注意,使用此解决方案,在chrome中,您将获得两次对处理程序的调用(每个事件一次),因此如果您关心它,那么您需要防范它。

因为它会在您停止移动鼠标时触发事件,然后在您释放鼠标按钮时再次触发事件。

答案 4 :(得分:4)

Andrew Willem的解决方案不兼容移动设备。

以下是他的第二个解决方案的修改,该解决方案适用于Edge,IE,Opera,FF,Chrome,iOS Safari和移动设备(我可以测试):

更新1:删除&#34; requestAnimationFrame&#34;部分,因为我同意它没有必要:

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

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

更新2:回应安德鲁2016年6月2日更新的答案:

谢谢,安德鲁 - 它似乎适用于我能找到的每个浏览器(赢桌面:IE,Chrome,Opera,FF; Android Chrome,Opera和FF,iOS Safari)。

更新3:if(&#34; oninput in slider)解决方案

以下内容似乎适用于所有上述浏览器。 (我现在找不到原始来源。)我正在使用它,但它随后在IE上失败了,所以我去寻找另一个,因此我最终到了这里。

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

但在我检查你的解决方案之前,我注意到这在IE中再次运行 - 也许还有其他一些冲突。

答案 5 :(得分:2)

对于良好的跨浏览器行为和可理解的代码,最好是结合使用onchange属性:

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

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

<span id="valBox">
  </span>
&#13;
&#13;
&#13;

使用 oninput 时,该值会直接更改。

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

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

<span id="valBox">
  </span>
&#13;
&#13;
&#13;

答案 6 :(得分:0)

另一种方法-只需在元素上设置一个标志,指示应该处理哪种类型的事件:

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

答案 7 :(得分:0)

我将此作为答案发布,以防您像我一样无法弄清楚为什么范围类型输入在任何移动浏览器上都不起作用。如果您在笔记本电脑上开发移动应用程序并使用响应模式来模拟触摸,您会注意到当您激活触摸模拟器时范围甚至不会移动。当您停用它时,它开始移动。我持续了 2 天尝试我能找到的关于这个主题的每一段代码,但无法让它在我的生活中发挥作用。我在这篇文章中提供了一个可行的解决方案。

移动浏览器和混合应用

移动浏览器使用名为 Webkit for iOSWebView for Android 的组件运行。 WebView/WebKit 使您能够将 Web 浏览器嵌入到您的活动布局中,该浏览器没有任何 chrome 或 firefox(浏览器)控件,包括窗口框架、菜​​单、工具栏和滚动条。换句话说,移动浏览器缺少许多通常在常规浏览器中可以找到的 Web 组件。这是范围类型输入的问题。如果用户的浏览器不支持范围类型,它将回退并将其视为文本输入。这就是为什么在激活触摸模拟器时无法移动范围的原因。

<块引用>

点击此处阅读更多信息 browser compatibility

jQuery 滑块

jQuery 提供了一个以某种方式与触摸模拟配合使用的滑块,但它不稳定且不是很平滑。这对我来说并不令人满意,它可能也不适合你,但如果你将它与 jqueryUi 结合起来,你可以让它工作得更顺畅。

最佳解决方案:范围触摸

如果您在笔记本电脑上开发混合应用,您可以使用一个简单易用的库来启用范围类型输入以处理触摸事件。

这个库叫做 Range Touch

DEMO

<块引用>

有关此问题的更多信息,请查看此线程 here

Recreating the HTML5 range input for Mobile Safari (webkit)?

答案 8 :(得分:-3)

您可以使用JavaScript&#34; ondrag&#34;连续射击的事件。它优于&#34;输入&#34;由于以下原因:

  1. 浏览器支持。

  2. 可以区分&#34; ondrag&#34;和&#34;改变&#34;事件。 &#34;输入&#34;不管是拖拽还是改变。

  3. 在jQuery中:

    $('#sample').on('drag',function(e){
    
    });
    

    参考: http://www.w3schools.com/TAgs/ev_ondrag.asp