在Firefox中拖拽时,input type=range上的onchange事件不会触发

当我使用<input type="range">时,只有当我们将滑块放到一个新位置,Chrome和其他工具在滑块被拖动时触发onchange事件时,Firefox才会触发onchange事件。

如何在Firefox中进行拖拽?

function showVal(newVal){
document.getElementById("valBox").innerHTML=newVal;
}
<span id="valBox"></span>
<input type="range" min="5" max="10" step="1" onchange="showVal(this.value)">

398090 次浏览

显然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线程以获取更多信息。

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

由Jamrelian在他的评论中提供的答案。

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

不过要注意Jaime的这句话

只是注意,有了这个解决方案,在chrome你会得到两个调用的处理程序(一个每个事件),所以如果你关心,那么你需要防范它。

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

更新

相对于change事件和input事件导致功能触发两次,这几乎不是一个问题。

如果你有一个函数在input上触发,那么当change事件触发时,就不太可能出现问题。

当你拖动范围输入滑块时,input会迅速触发。担心最后再触发一个函数调用就像担心一滴水,而不是input事件。

甚至包括change事件的原因是为了浏览器兼容性(主要是IE)。

更新:我把这个答案留在这里作为一个例子,如何使用鼠标事件在桌面(而不是移动)浏览器中使用范围/滑动条交互。然而,我现在也写了一个完全不同的,我相信,更好的答案在这个页面的其他地方,使用不同的方法来提供跨浏览器的桌面-和移动解决这个问题。

最初的回答:

概述:一个跨浏览器的纯JavaScript(即no-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'...会不断更新。

我在这里报告两种策略,使用普通JavaScript(即没有jQuery),通过使用mousedown, mouemove和(可能的)mouseup事件,在所有浏览器中获得相同的连续范围值报告。

策略1:时间短但效率低

如果你喜欢更短的代码而不是更高效的代码,你可以使用第一种解决方案,它使用鼠标向下和鼠标移动,但不使用鼠标向上。这将根据需要读取滑块,但在任何鼠标悬停事件期间继续不必要地触发滑块,即使当用户没有单击并因此没有拖动滑块时也是如此。它本质上是在'mousedown'事件之后和' mouemove '事件期间读取范围值,使用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"/>

Strategy 2: Longer but more efficient

If you need more efficient code and can tolerate longer code length, then you can use the following solution which uses mousedown, mousemove and mouseup. This also reads the slider as needed, but appropriately stops reading it as soon as the mouse button is released. The essential difference is that is only starts listening for 'mousemove' after 'mousedown', and it stops listening for 'mousemove' after 'mouseup'.

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: Fuller explanation of the need for, and implementation of, the above work-arounds

The following code more fully demonstrates numerous aspects of this strategy. Explanations are embedded in the demonstration:

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>

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

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

更新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:对安德鲁2016年6月2日的更新答案的回应:

谢谢,安德鲁-这似乎在我能找到的每一个浏览器(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中又工作了-也许有一些其他的冲突。

简介:

我在这里提供了一种无需jquery的跨浏览器桌面和移动设备的能力,以一致地响应范围/滑动条交互,这在当前的浏览器中是不可能的。它本质上迫使所有浏览器在其on("change"...on("input"...事件中模拟IE11的on("change"...事件。新功能是…

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. 最后的鼠标移动(或触摸结束)

下表显示了至少三种不同的桌面浏览器在响应上述场景时的不同行为:

表显示浏览器差异,他们响应的事件和时间

解决方案:

onRangeChange函数为范围/滑动条交互提供了一致且可预测的跨浏览器响应。它迫使所有浏览器按照下表的方式运行:

表显示行为的所有浏览器使用建议的解决方案

在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是你想要在类似__abc3的事件上调用的侦听器/处理程序函数。

如果需要的话,你的监听器可以是无参数的,也可以使用event参数,也就是说,根据你的需要,以下任何一种都可以工作:

var myListener = function() {...

var myListener = function(evt) {...

(从input元素中删除事件监听器(例如使用removeEventListener)没有在这个答案中解决。)

演示说明:

在下面的代码片段中,函数onRangeChange提供了通用的解决方案。代码的其余部分只是一个示例,用于演示它的使用。任何以my...开头的变量都与通用解决方案无关,只是为了演示而出现。

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

  • 在IE11中,场景2和场景3中,A行和C行值都发生变化,而B行值不变。
  • 在Chrome浏览器和Safari浏览器中,场景2和场景3中B行和C行值都发生变化,场景5中A行值只发生变化。
  • 在Firefox浏览器中,只有场景5中A行值发生变化,所有5个场景中B行值发生变化,场景2和场景3中C行值发生变化。
  • 在上述所有浏览器中,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的回答的启发。另一个答案表明,“输入”;和“;change"事件可以合并,生成的代码可以在桌面浏览器和移动浏览器中工作。然而,该答案中的代码导致隐藏的“extra”;事件被触发,这本身是有问题的,而不同浏览器触发的事件不同,这是一个进一步的问题。我在这里的实现解决了这些问题。

关键词:

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

为了获得良好的跨浏览器行为和可理解的代码,最好是将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>

你可以使用JavaScript的"ondrag"事件来连续触发。它优于“输入”,原因如下:

  1. 浏览器的支持。

  2. 可以区分“ondrag”和“change”事件。“输入”触发拖拽和更改。

jQuery:

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


});
< p >参考: http://www.w3schools.com/TAgs/ev_ondrag.asp < / p >

还有另一种方法——在元素上设置一个标志,表明应该处理哪种类型的事件:

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

我把这个作为一个答案,以防你和我一样,不明白为什么range类型输入在任何移动浏览器上都不能工作。如果你在笔记本电脑上开发移动应用程序,并使用响应模式来模拟触摸,你会注意到,当你激活触摸模拟器时,范围甚至不会移动。当你关闭它时,它就开始移动。我花了两天时间尝试了我能找到的每一段关于这个主题的代码,但都不能让它为我的生活工作。在这篇文章中,我提供了一个可行的解决方案。

移动浏览器和混合应用

移动浏览器使用名为面向iOS的WebkitWebView for Android的组件运行。WebView/WebKit使您能够嵌入一个web浏览器,它没有任何chrome或firefox(浏览器)控件,包括窗口框架,菜单,工具栏和滚动条到您的活动布局中。换句话说,移动浏览器缺少许多常规浏览器中常见的web组件。这就是范围类型输入的问题。如果用户的浏览器不支持范围类型,它将退回并将其视为文本输入。这就是为什么当触摸模拟器被激活时,你不能移动范围。

浏览器兼容性上阅读更多

jQuery滑块

jQuery提供了一个滑块,以某种方式与触摸模拟工作,但它是起伏的,不是很光滑。它不能满足我,它可能不会为你,但你可以使它工作更顺利,如果你结合jqueryUi。

最佳解决方案:范围触摸

如果你在笔记本电脑上开发混合应用程序,有一个简单易用的库可以让范围类型输入与触摸事件一起工作。

这个库被称为接触范围

DEMO

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

重新创建HTML5范围输入移动Safari (webkit)?< / >

这个工作为我在反应引导

           <Form.Control
type="range"
className="slider"
min="0.1"
max="10"
step={0.1}
value={intensity}
onChange={(e: any) => {
if (!e.target.onInputHasBeenCalled) {
setIntensity(parseFloat(e.target.value.toString()));
e.target.onInputHasBeenCalled = true;
} else {
e.target.onInputHasBeenCalled = false;
}
}}
/>