295

当我玩 时<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

9 回答 9

520

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

于 2013-09-28T13:02:39.473 回答
48

概括:

我在这里提供了一个无 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 月上旬,不同浏览器对范围/滑块使用的响应方式有所不同。五个场景是相关的:

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

wheremyRangeInputElmt是您想要的<input type="range">DOM 元素,并且是您希望在-like 事件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 行针对所有五个方案更改,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 输入类型范围滑块事件改变输入浏览器兼容性跨浏览器桌面移动无jQuery

于 2016-06-03T21:48:38.493 回答
38

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

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

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

不过请注意 Jaime 的评论

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

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

更新

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

如果您有一个函数在 on 上触发,则事件触发input时极不可能出现问题。change

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

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

于 2015-10-09T04:50:49.597 回答
33

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

原答案:

简介:一种跨浏览器、纯 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&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>

于 2016-02-27T22:20:16.737 回答
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:对 Andrew 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 中再次起作用 - 也许还有其他一些冲突。

于 2016-06-01T08:09:26.857 回答
4

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

于 2016-09-15T07:37:07.793 回答
1

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

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);
        }
    };
}
于 2018-12-21T12:59:56.497 回答
1

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

移动浏览器和混合应用

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

在此处阅读有关浏览器兼容性的更多信息

jQuery 滑块

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

最佳解决方案:Range Touch

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

这个库被称为Range Touch

演示

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

为 Mobile Safari (webkit) 重新创建 HTML5 范围输入?

于 2021-01-10T23:59:21.373 回答
-3

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

  1. 浏览器支持。

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

在 jQuery 中:

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

});

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

于 2017-01-23T16:01:43.993 回答