Sunday 24 July 2016

A pure javascript numbers range slider

Recently I needed to use a range slider in an interface where it was to be used to select an interval of values. I assumed I could use the HTML5 range-control, <input type="range">, but it turned out that it was hard to get the look and feel I wanted. I had to stack two range controls on top of each other, position them and try to style them with all kinds of vendor specific attributes. When it finally worked and look OK in Chrome and Firefox, I took a look in Microsoft Edge and gave up.

Instead, I built this little javascript control instead. This simple version with 200 lines of javascript is available on github.com/asalilje/rangeslider and a demo of the range slider can be found at asalilje.github.io/rangeslider/.

Elements of the range slider

  • A static background track representing the total range.
  • Static labels for the total range's starting and ending values.
  • Two draggable handles for selecting the interval.
  • An interval track marking the currently selected range between the handles.
  • Input fields for both handles, showing the current value selected. It's also possible to input another value and get the handles to move.


Basic concept

A number of event listeners are added to the elements, responding to touch as well as mouse movements. When a handle is clicked, or touched, it is set to active. When the mouse or finger is moved, a function is triggered that:
  • Changes the position of the active handle so it looks like it's being dragged.
  • Calculates the current value based on the current handle position.
  • Changes the size and position of the interval track.
  • Writes the current values to the input fields.
When the mouse button is released, or the touch ends, the active handle is reset and all actions stopped.

A linear range

The simple version of a range slider just calculates the values in a linear fashion. Say you have a background track that is 500 pixels wide and a numbers range going from 0 to 100. By first calculating how many percent of the total track the handle has reached, that same percentage is used to calculate the current value.
const maxValue = 100;
const minValue = 0;
const value = maxValue - minValue;
const trackWidth = 500;
const currentPosition = 125;
const currentPercentage = 125/500*100; //25
const currentValue = value * (percentage/100); // 25
This calculation is quite simple to implement. But what if you want to use for instance the standard deviation to get a more precise scale closer to the median? Maybe the numbers range is very big and the precision should be less the bigger the numbers get. This was definitely one of my use cases.

A crazy range

To change the scale of the range I decided to make it possible to configure subranges. Giving the slider a set of values and the percentages they represent on the slider meant that I could split the total range into smaller subranges and get different scales for the different parts.

For instance, the values "0,100,500" mapped to the percentages "0,50,100" means that the values 0 to 100 stretches from 0 to 50% on the range, while 100 to 500 uses the remaining 50%. To calculate this we need to find the current value range the handle is in, calculate it's position within that range and then use the ratio of that range compared to the total range.
const prevRangeMaxValue = 0; // no previous range
const rangeStartValue = 0;
const rangeEndValue = 100;
const rangeValue = rangeEndValue - rangeStartValue;  // 100
const rangeStartPercent = 0;
const rangeRatio = 100/50; // the range's part of the total range
const trackWidth = 500;
const currentPosition = 125;
const currentPercentage = 125/500*100; // 25
const currentValue = (currentPercentage-rangeStartPercent) * (rangeRatio*rangeValue)
      / 100 + prevRangeMaxValue; // 50
Once we have this calculation in place, we can do all kinds of funny range scales by adding arrays of values and their matching percentages. The values to actually use for the subranges I calculate in the backend, cause you know, I'm not really a frontend coder.