输入类型 = 范围上的 onchange 事件在拖动时未在 Firefox 中触发
Posted
技术标签:
【中文标题】输入类型 = 范围上的 onchange 事件在拖动时未在 Firefox 中触发【英文标题】:onchange event on input type=range is not triggering in firefox while dragging 【发布时间】:2013-09-03 21:22:27 【问题描述】:当我使用<input type="range">
时,只有当我们将滑块放到一个新位置时,Firefox 才会触发 onchange 事件,Chrome 和其他人在拖动滑块时会触发 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)">
【问题讨论】:
如果范围元素有焦点,您可以使用箭头键移动滑块。在这种情况下,onchange
也不会触发。在解决这个问题时,我发现了这个问题。
【参考方案1】:
显然 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 了解更多信息。
【讨论】:
或$("#myelement").on("input change", function() doSomething(); );
使用 jQuery。
请注意,使用此解决方案,在 chrome 中,您将收到对处理程序的两次调用(每个事件一次),因此如果您关心它,那么您需要防范它。
我在移动 chrome (v34) 中未触发“更改”事件时遇到问题,但在等式中添加“输入”已为我解决了问题,同时在其他地方没有不利影响。跨度>
@Jaime:“如果你关心它,那么你需要防范它。”请问我该怎么做?
遗憾的是,onchange()
不适用于android Chrome
和ios Safari
等移动网络。有什么替代建议吗?【参考方案2】:
总结:
我在这里提供了一种非 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 之后的任何后续鼠标(或触摸)移动
在滑块任一端超过 1 或 2 后的任何后续鼠标(或触摸)移动
最终鼠标向上(或触摸结束)
下表显示了至少三种不同的桌面浏览器在响应上述哪种场景时的行为有何不同:
解决方案:
onRangeChange
函数为范围/滑块交互提供一致且可预测的跨浏览器响应。它强制所有浏览器按照下表运行:
在 IE11 中,代码本质上允许一切按现状运行,即它允许 "change"
事件以其标准方式运行,而 "input"
事件无关紧要,因为它永远不会触发。在其他浏览器中,"change"
事件被有效地静音(以防止触发额外的,有时是不明显的事件)。此外,"input"
事件仅在范围/滑块的值更改时触发其侦听器。对于某些浏览器(例如 Firefox),这是因为侦听器在上述列表中的场景 1、4 和 5 中被有效地静音。
(如果您确实需要在场景 1、4 和/或 5 中激活监听器,您可以尝试合并 "mousedown"
/"touchstart"
、"mousemove"
/"touchmove"
和/或 @987654342 @/"touchend"
events。这样的解决方案超出了这个答案的范围。)
移动浏览器的功能:
我已经在桌面浏览器中测试过这段代码,但没有在任何移动浏览器中测试过。但是,在another answer on this page MBourne 中表明我的解决方案 "...似乎适用于我能找到的所有浏览器(Win 桌面:IE、Chrome、Opera、FF;Android Chrome、Opera 和 FF、iOS Safari )"。 (感谢 MBourne。)
用法:
要使用此解决方案,请在您自己的代码中包含上面摘要中的onRangeChange
函数(简化/缩小)或下面的演示代码 sn-p(功能相同但更不言自明)。调用如下:
onRangeChange(myRangeInputElmt, myListener);
其中myRangeInputElmt
是您想要的<input type="range">
DOM 元素,myListener
是您想要在类似"change"
的事件上调用的侦听器/处理程序函数。
如果需要,您的侦听器可以是无参数的,也可以使用 event
参数,即根据您的需要,以下任一方法都可以:
var myListener = function() ...
或
var myListener = function(evt) ...
(从input
元素中删除事件监听器(例如,使用removeEventListener
)未在此答案中解决。)
演示说明:
在下面的代码sn-p中,函数onRangeChange
提供了通用的解决方案。其余的代码只是一个示例来演示它的使用。任何以my...
开头的变量都与通用解决方案无关,仅用于演示。
演示显示范围/滑块值以及标准"change"
、"input"
和自定义"onRangeChange"
事件已触发的次数(分别为 A、B 和 C 行)。在不同的浏览器中运行这个 sn-p 时,在与范围/滑块交互时请注意以下几点:
演示代码:
// 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's answer。另一个答案表明可以合并“输入”和“更改”事件,并且生成的代码可以在桌面和移动浏览器中运行。但是,该答案中的代码会导致触发隐藏的“额外”事件,这本身就是有问题的,并且触发的事件在浏览器之间有所不同,这是另一个问题。我在这里的实现解决了这些问题。
关键字:
javascript 输入类型范围滑块事件改变输入浏览器兼容性跨浏览器桌面移动 no-jQuery
【讨论】:
【参考方案3】:我将此作为答案发布,因为它应该是它自己的答案,而不是在不太有用的答案下发表评论。我发现这种方法比接受的答案要好得多,因为它可以将所有 js 保存在与 HTML 不同的文件中。
Jamrelian 在接受的答案下的评论中提供的答案。
$("#myelement").on("input change", function()
//do something
);
请注意 Jaime 的这条评论
请注意,使用此解决方案,在 chrome 中,您将收到对处理程序的两次调用(每个事件一次),因此如果您关心它,那么您需要防范它。
因为它会在您停止移动鼠标时触发该事件,然后在您释放鼠标按钮时再次触发。
更新
关于导致功能触发两次的change
事件和input
事件,这几乎不是问题。
如果您有一个函数在input
上触发,那么在change
事件触发时出现问题的可能性极小。
input
在您拖动范围输入滑块时快速触发。担心最后再触发一个函数调用就像担心一滴水与海洋相比,即input
事件。
甚至包含change
事件的原因是为了浏览器兼容性(主要是与IE)。
【讨论】:
plus 1 适用于 Internet Explorer 的唯一解决方案!你在其他浏览器上也测试过吗? 它是 jQuery,所以它不工作的可能性非常低。我还没有看到它自己在任何地方都不起作用。它甚至可以在移动设备上运行,这很棒。【参考方案4】:更新:我在这里留下这个答案作为如何使用鼠标事件在桌面(但不是移动)浏览器中使用范围/滑块交互的示例。但是,我现在还在此页面的其他地方写了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'...
会不断更新。
我在这里报告 2 种策略,通过使用 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 B.</li>
<li>To see the importance of 'requestAnimationFrame' on 'mousedown', click a new location on the slider and compare C (incorrect) and D (correct).</li>
<li>To see the importance of 'requestAnimationFrame' on 'mousemove', click and drag but do not release the slider, and compare E (often 1 pixel behind) and F (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 (incorrect), F (incorrect) and H (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 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 I.</li>
</ol>
【讨论】:
非常好的信息。有时甚至可以添加触摸事件?touchmove
应该足够了,我认为在这种情况下它可以像mousemove
一样对待。
@nick,请在此页面上查看我的其他答案,了解提供移动浏览器功能的不同解决方案。
这不是一个可访问的答案,因为键盘导航不会触发事件,因为它们都是以鼠标为中心的
@LocalPCGuy,你是对的。我的回答破坏了滑块的内置键盘可控性。我已更新代码以恢复键盘控制。如果您知道我的代码破坏内置可访问性的任何其他方式,请告诉我。【参考方案5】:
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:对 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 中再次起作用 - 可能还有其他冲突。
【讨论】:
我确认您的解决方案适用于两种不同的桌面浏览器:Mac Firefox 和 Windows IE11。我个人无法检查任何移动可能性。但这看起来像是对我的答案的一个很好的更新,将其扩展到包括移动设备。 (我假设“输入”和“更改”在桌面系统上接受鼠标输入,在移动系统上接受触摸输入。)非常好的工作,应该广泛适用,值得得到认可和实施! 进一步挖掘后,我意识到您的解决方案有几个缺点。首先,我认为 requestAnimationFrame 是不必要的。其次,代码会触发额外的事件(在某些浏览器中,至少有 3 个事件,例如 mouseup)。第三,某些浏览器触发的确切事件不同。因此,我根据您使用“输入”和“更改”事件组合的最初想法提供了一个单独的答案,为您提供了最初的灵感。【参考方案6】:为了获得良好的跨浏览器行为和可理解的代码,最好将 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>
【讨论】:
【参考方案7】:另一种方法 - 只需在元素上设置一个标志,指示应处理哪种类型的事件:
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);
;
【讨论】:
【参考方案8】:如果您像我一样无法弄清楚为什么范围类型输入在任何移动浏览器上都不起作用,我将其发布为答案。如果您在笔记本电脑上开发移动应用程序并使用响应模式来模拟触摸,您会注意到当您激活触摸模拟器时范围甚至不会移动。当您停用它时,它开始移动。我继续尝试了 2 天,尝试了我能找到的关于这个主题的每一段代码,但无法让它为我的生活服务。我在这篇文章中提供了一个可行的解决方案。
移动浏览器和混合应用
移动浏览器使用名为Webkit for iOS 和WebView 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)?
【讨论】:
【参考方案9】:您可以使用 JavaScript “ondrag” 事件连续触发。由于以下原因,它比“输入”更好:
浏览器支持。
可以区分“ondrag”和“change”事件。拖动和更改都会触发“输入”。
在 jQuery 中:
$('#sample').on('drag',function(e)
);
参考: http://www.w3schools.com/TAgs/ev_ondrag.asp
【讨论】:
以上是关于输入类型 = 范围上的 onchange 事件在拖动时未在 Firefox 中触发的主要内容,如果未能解决你的问题,请参考以下文章
反应 Typescript 输入 onChange 事件类型?
html5输入类型范围总是指向最小值并且没有在文本输入上获得onChange