SUMMARY:
I provide here a no-jQuery cross-browser desktop-and-mobile ability to consistently respond to range/slider interactions, something not possible in current browsers. It essentially forces all browsers to emulate IE11's on("change"...
event for either their on("change"...
or on("input"...
events. The new function is...
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);});
}
...where r
is your range input element and f
is your listener. The listener will be called after any interaction that changes the range/slider value but not after interactions that do not change that value, including initial mouse or touch interactions at the current slider position or upon moving off either end of the slider.
Problem:
As of early June 2016, different browsers differ in terms of how they respond to range/slider usage. Five scenarios are relevant:
The following table shows how at least three different desktop browsers differ in their behaviour with respect to which of the above scenarios they respond to:
Solution:
The onRangeChange
function provides a consistent and predictable cross-browser response to range/slider interactions. It forces all browsers to behave according to the following table:
In IE11, the code essentially allows everything to operate as per the status quo, i.e. it allows the "change"
event to function in its standard way and the "input"
event is irrelevant as it never fires anyway. In other browsers, the "change"
event is effectively silenced (to prevent extra and sometimes not-readily-apparent events from firing). In addition, the "input"
event fires its listener only when the range/slider's value changes. For some browsers (e.g. Firefox) this occurs because the listener is effectively silenced in scenarios 1, 4 and 5 from the above list.
(If you truly require a listener to be activated in either scenario 1, 4 and/or 5 you could try incorporating "mousedown"
/"touchstart"
, "mousemove"
/"touchmove"
and/or "mouseup"
/"touchend"
events. Such a solution is beyond the scope of this answer.)
Functionality in Mobile Browsers:
I have tested this code in desktop browsers but not in any mobile browsers. However, in another answer on this page MBourne has shown that my solution here "...appears to work in every browser I could find (Win desktop: IE, Chrome, Opera, FF; Android Chrome, Opera and FF, iOS Safari)". (Thanks MBourne.)
Usage:
To use this solution, include the onRangeChange
function from the summary above (simplified/minified) or the demo code snippet below (functionally identical but more self-explanatory) in your own code. Invoke it as follows:
onRangeChange(myRangeInputElmt, myListener);
where myRangeInputElmt
is your desired <input type="range">
DOM element and myListener
is the listener/handler function you want invoked upon "change"
-like events.
Your listener may be parameter-less if desired or may use the event
parameter, i.e. either of the following would work, depending on your needs:
var myListener = function() {...
or
var myListener = function(evt) {...
(Removing the event listener from the input
element (e.g. using removeEventListener
) is not addressed in this answer.)
Demo Description:
In the code snippet below, the function onRangeChange
provides the universal solution. The rest of the code is simply an example to demonstrate its use. Any variable that begins with my...
is irrelevant to the universal solution and is only present for the sake of the demo.
The demo shows the range/slider value as well as the number of times the standard "change"
, "input"
and custom "onRangeChange"
events have fired (rows A, B and C respectively). When running this snippet in different browsers, note the following as you interact with the range/slider:
Demo Code:
// main function for emulating IE11's "change" event:_x000D_
_x000D_
function onRangeChange(rangeInputElmt, listener) {_x000D_
_x000D_
var inputEvtHasNeverFired = true;_x000D_
_x000D_
var rangeValue = {current: undefined, mostRecent: undefined};_x000D_
_x000D_
rangeInputElmt.addEventListener("input", function(evt) {_x000D_
inputEvtHasNeverFired = false;_x000D_
rangeValue.current = evt.target.value;_x000D_
if (rangeValue.current !== rangeValue.mostRecent) {_x000D_
listener(evt);_x000D_
}_x000D_
rangeValue.mostRecent = rangeValue.current;_x000D_
});_x000D_
_x000D_
rangeInputElmt.addEventListener("change", function(evt) {_x000D_
if (inputEvtHasNeverFired) {_x000D_
listener(evt);_x000D_
}_x000D_
}); _x000D_
_x000D_
}_x000D_
_x000D_
// example usage:_x000D_
_x000D_
var myRangeInputElmt = document.querySelector("input" );_x000D_
var myRangeValPar = document.querySelector("#rangeValPar" );_x000D_
var myNumChgEvtsCell = document.querySelector("#numChgEvtsCell");_x000D_
var myNumInpEvtsCell = document.querySelector("#numInpEvtsCell");_x000D_
var myNumCusEvtsCell = document.querySelector("#numCusEvtsCell");_x000D_
_x000D_
var myNumEvts = {input: 0, change: 0, custom: 0};_x000D_
_x000D_
var myUpdate = function() {_x000D_
myNumChgEvtsCell.innerHTML = myNumEvts["change"];_x000D_
myNumInpEvtsCell.innerHTML = myNumEvts["input" ];_x000D_
myNumCusEvtsCell.innerHTML = myNumEvts["custom"];_x000D_
};_x000D_
_x000D_
["input", "change"].forEach(function(myEvtType) {_x000D_
myRangeInputElmt.addEventListener(myEvtType, function() {_x000D_
myNumEvts[myEvtType] += 1;_x000D_
myUpdate();_x000D_
});_x000D_
});_x000D_
_x000D_
var myListener = function(myEvt) {_x000D_
myNumEvts["custom"] += 1;_x000D_
myRangeValPar.innerHTML = "range value: " + myEvt.target.value;_x000D_
myUpdate();_x000D_
};_x000D_
_x000D_
onRangeChange(myRangeInputElmt, myListener);
_x000D_
table {_x000D_
border-collapse: collapse; _x000D_
}_x000D_
th, td {_x000D_
text-align: left;_x000D_
border: solid black 1px;_x000D_
padding: 5px 15px;_x000D_
}
_x000D_
<input type="range"/>_x000D_
<p id="rangeValPar">range value: 50</p>_x000D_
<table>_x000D_
<tr><th>row</th><th>event type </th><th>number of events </th><tr>_x000D_
<tr><td>A</td><td>standard "change" events </td><td id="numChgEvtsCell">0</td></tr>_x000D_
<tr><td>B</td><td>standard "input" events </td><td id="numInpEvtsCell">0</td></tr>_x000D_
<tr><td>C</td><td>new custom "onRangeChange" events</td><td id="numCusEvtsCell">0</td></tr>_x000D_
</table>
_x000D_
Credit:
While the implementation here is largely my own, it was inspired by MBourne's answer. That other answer suggested that the "input" and "change" events could be merged and that the resulting code would work in both desktop and mobile browsers. However, the code in that answer results in hidden "extra" events being fired, which in and of itself is problematic, and the events fired differ between browsers, a further problem. My implementation here solves those problems.
Keywords:
JavaScript input type range slider events change input browser compatability cross-browser desktop mobile no-jQuery