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


252

在与一起玩时<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)">


4
如果范围元素具有焦点,则可以使用箭头键移动滑块。在这种情况下,也onchange不会触发。正是在解决该问题的过程中,我才发现了这个问题。
Sildoreth 2015年

Answers:


452

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

但是,oninputIE10不支持该功能,因此最好的选择是将两个事件处理程序结合起来,如下所示:

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

查看此Bugzilla线程以获取更多信息。


113
$("#myelement").on("input change", function() { doSomething(); });使用jQuery。
Alex Vang 2013年

19
只需注意,使用此解决方案,在chrome中,您将收到两次对处理程序的调用(每个事件一个),因此,如果您对此有所关注,则需要注意这一点。
Jaime 2014年

3
我遇到了“更改”事件无法在移动chrome(v34)中触发的问题,但是在方程式中添加“输入”已经为我解决了这一问题,而在其他地方没有不利影响。
brins0 2014年

7
@Jaime:“ 如果您对此有所关注,那么就需要警惕。 ”请问我该怎么办?

2
可悲的是,onchange()没有移动网络上一样工作Android ChromeiOS Safari。还有其他建议吗?
Alston

41

摘要:

我在这里提供了无jQuery跨浏览器的桌面和移动功能,能够始终如一地响应范围/滑块交互,这是当前浏览器无法实现的。实质上,它会强制所有浏览器on("change"...为其on("change"...on("input"...事件模拟IE11的事件。新功能是...

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要在"change"类似事件时调用的侦听器/处理函数。

如果需要,您的侦听器可以是无参数的,也可以使用该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中,行A的值仅针对方案5发生更改,行B的所有五个方案均发生变化,行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


31

我将其发布为答案,因为它应该是自己的答案,而不是在不太有用的答案下发表评论。我发现此方法比接受的答案好得多,因为它可以将所有js与HTML放在单独的文件中。

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

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

不过请注意Jaime的评论

只需注意,使用此解决方案,在chrome中,您将收到两次调用处理程序的调用(每个事件一个),因此,如果您对此有所关注,则需要注意这一点。

如上图所示,当您停止移动鼠标,然后再释放鼠标按钮时,它将触发该事件。

更新资料

关于change事件和input导致功能触发两次的事件,这几乎不是问题。

如果您启动了某个功能input,那么在change事件触发时就不太可能出现问题。

input拖动范围输入滑块时会快速触发。最后,担心再有一个函数调用触发,就像担心与input事件海洋相比,一滴水一样。

甚至完全包含change事件的原因是出于浏览器兼容性的考虑(主要是与IE兼容)。


加1对于与Internet Explorer一起使用的唯一解决方案!!您是否也在其他浏览器上进行过测试?
杰西卡

1
它是jQuery,因此无法正常工作的可能性很小。我还没有看到它在任何地方都不起作用。它甚至可以在移动设备上运行,这很棒。
Daniel Tonon 2015年

30

更新:我在这里留下这个答案,作为一个示例,说明如何使用鼠标事件在台式机(而非移动设备)浏览器中使用范围/滑块交互。但是,我现在也写了一个完全不同的文章,并且我相信,此页上的其他地方会提供更好的答案,它使用不同的方法来提供跨浏览器的台式机和移动设备解决方案。

原始答案:

简介:一种跨浏览器的纯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'...会不断更新。

我在这里报告两种策略,通过使用mousedown,mousemove和(可能)mouseup事件,在使用香草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"/>

演示:有关上述变通办法的必要性和实现的完整说明

以下代码更全面地演示了此策略的许多方面。演示中嵌入了说明:


真的很好的信息。也许甚至在某些时候添加了联系事件?touchmove应该足够了,我认为可以像mousemove在这种情况下一样对待它。
尼克

@nick,请参阅此页面上的其他答案,以获取确实提供移动浏览器功能的其他解决方案。
Andrew Willems

这不是可访问的答案,因为键盘导航不会触发事件,因为它们都是以鼠标为中心的
LocalPCGuy

@LocalPCGuy,您是对的。我的答案打破了滑块的内置键盘可控制性。我已经更新了恢复键盘控制的代码。如果您知道我的代码破坏了内置可访问性的任何其他方式,请告诉我。
安德鲁·威廉姆斯

7

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(“在滑块上输入”)解决方案

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

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

但是在检查您的解决方案之前,我注意到它在IE中又可以正常工作-也许还有其他冲突。


1
我确认您的解决方案可在两种不同的桌面浏览器中使用:Mac Firefox和Windows IE11。我个人还无法检查任何移动可能性。但是,这似乎是我的回答的重大更新,将其范围扩大到了移动设备。(我假设“输入”和“更改”既可以接受台式机系统上的鼠标输入,也可以接受移动系统上的触摸输入。)非常棒的工作,应该广泛地适用,值得得到认可和实现!
Andrew Willems

1
进一步研究之后,我意识到您的解决方案有几个缺点。首先,我认为requestAnimationFrame是不必要的。其次,代码会触发额外的事件(在某些浏览器中,例如,在发生mouseup时至少会发生3个事件)。第三,某些浏览器触发的确切事件有所不同。因此,我根据您使用“输入”和“变更”事件的组合的最初想法提供了单独的答案,使您对最初的灵感有所赞赏。
Andrew Willems

2

为了实现良好的跨浏览器行为和易于理解的代码,最好是结合使用以下形式的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>


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

-3

您可以使用JavaScript“ ondrag”事件连续触发。由于以下原因,它比“输入”要好:

  1. 浏览器支持。

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

在jQuery中:

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

});

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.