Slider (Multirange)

Slider (Multirange)

Overview

A multi-range or multi-thumb slider is built on the same concept as a simple slider except that there are two sliding controls, to allow users to set a minimum value and a maximum value. Mobile support is a problem with multi-range sliders, so it is wise to supplement the slider with editable text fields or other similar controls that give users an alternative way to specify the range.



Turn on a screen reader to experience this example in action.

Attribute/Option Description
sliderColor This attribute/option specifies color for the main slider / ruler. If this option is not specified, a default color is used.
markerColor This attribute/option specifies color for the marker. If this option is not specified, a default color is used.
getTextValue This option specifies a function that will convert a value into required text that could be more meaningful for scale markers. If this option is not specified, value will be displayed in integer form.
getTextValueDesc This option specifies a function that will convert a value into required text description that could be more meaningful for screen readers. If this option is not specified, value will be displayed in integer form.
noOfScales This option specifies number of markings/scales to be displayed on the slider. By default, it will have 6 markings.
width This attribute/option specifies the width of the slider widget. If this attribute/ option is not specified, width will be set with a default value.
ariaDescribedBy This attribute/option specifies id(s) of the label(s)/heading(s)/other element(s) that describe the sliders. If this attribute/ option is not specified, the sliders will not have the ARIA description.
Please Note: This option does not work with Narrator / Microsoft Edge browser combination.

Timeframe for an audio clip

Price range for purchasing a house

Month range

HTML Source Code

<table class="data">
    <tr>
        <th width="150px" >Attribute/Option</th>
        <th>Description</th>
    </tr>
    <tr>
        <td><code>sliderColor</code></td>
        <td>This attribute/option specifies color for the main slider / ruler.
            If this option is not specified, a default color is used.
        </td>
        </tr>
    <tr>
        <td><code>markerColor</code></td>
        <td>This attribute/option specifies color for the marker.
            If this option is not specified, a default color is used.
        </td>
    </tr>
    <tr>
        <td><code>getTextValue</code></td>
        <td>This option specifies a function that will convert a value into required text that could be more meaningful for scale markers. 
            If this option is not specified, value will be displayed in integer form.                    
        </td>
    </tr>
    <tr>
        <td><code>getTextValueDesc</code></td>
        <td>This option specifies a function that will convert a value into required text description that could be more meaningful for screen readers. 
            If this option is not specified, value will be displayed in integer form.                    
        </td>
    </tr>
    <tr>
        <td><code>noOfScales</code></td>
        <td>This option specifies number of markings/scales to be displayed on the slider. By default, it will have 6 markings.
        </td>
    </tr>
    <tr>
        <td><code>width</code></td>
        <td>This attribute/option specifies the width of the slider widget.
            If this attribute/ option is not specified, width will be set with a default value.    
        </td>
    </tr>    
    <tr>
        <td><code>ariaDescribedBy</code></td>
        <td>This attribute/option specifies id(s) of the label(s)/heading(s)/other element(s) that describe the sliders.
            If this attribute/ option is not specified, the sliders will not have the ARIA description. <br/>
            Please Note: This option does not work with Narrator / Microsoft Edge browser combination. 
        </td>
    </tr>    
</table>

<div id="slider-seek-label" ><h2>Timeframe for an audio clip</h2></div>
<div class="slider-seek" id="media-seek-slider" ></div>

<div id="slider-house-label" ><h2>Price range for purchasing a house</h2></div>
<div class="slider-seek" id="house-price-slider"  ></div>

<div id="slider-month-label" ><h2>Month range</h2></div>
<div class="slider-seek" id="month-slider"  ></div>
  
  <!---
This component has been adapted from an example provided by the W3C, in accordance with the W3C Software and Document License https://www.w3.org/copyright/software-license-2023/
-->

JavaScript Source Code

class SliderMultirange {
    constructor(domNode, min, max, current, higherRange, options ) {
      this.domNode = domNode;
      this.minValue = min;
      this.maxValue = max;
      this.currentValue = current;
      this.higherRange = higherRange;
      
      
      this.isMoving = false;
  
      if ( typeof options != "object" ) options = {};
      this.sliderColor = "";
      if(typeof options["sliderColor"] === "string")
      {
          this.domNode.style.color = options["sliderColor"];
          this.sliderColor = options["sliderColor"];
      }
      this.markerColor = "";
      if(typeof options["markerColor"] === "string")
          this.markerColor = options["markerColor"];
      this.getTextValue = null;
      if(typeof options["getTextValue"] === "function")
          this.getTextValue = options["getTextValue"];
      this.getTextValueDesc = null;
      if(typeof options["getTextValueDesc"] === "function")
          this.getTextValueDesc = options["getTextValueDesc"];
  
      this.width = 300;
      if( (typeof options["width"] === "string") || (typeof options["width"] === "number") )
          this.width = options["width"];
      this.noOfScales = 5;
      if(typeof options["noOfScales"] === "number")
          this.noOfScales = options["noOfScales"];
      this.ariaDescribedBy = "";
      if(typeof options.ariaDescribedBy === "string")
          this.ariaDescribedBy = options.ariaDescribedBy;
      
      
      
      this.domNode.innerHTML = this.getHTMLSetup();
      this.svgNode = domNode.querySelector('svg');
      this.svgPoint = this.svgNode.createSVGPoint();
  
      this.railNode = domNode.querySelector('.rail');
  // Dimensions of the slider focus ring, thumb and rail
  this.railHeight = parseInt(this.railNode.getAttribute('height'));
      this.railWidth = parseInt(this.railNode.getAttribute('width'));
      this.railY = parseInt(this.railNode.getAttribute('y'));
      this.railX = parseInt(this.railNode.getAttribute('x'));
  
      
      this.railNode.setAttribute('y', this.railY);
      this.railNode.setAttribute('x', this.railX);
      this.railNode.setAttribute('height', this.railHeight);
      //this.railNode.setAttribute('width', this.railWidth);
  
  
      this.sliderNode = domNode.querySelector('[role=slider]');
      this.sliderValueNode = this.sliderNode.querySelector('.value');
      this.sliderFocusNode = this.sliderNode.querySelector('.focus-ring');
      this.sliderThumbNode = this.sliderNode.querySelector('.thumb');
      this.valueLabelNodes = domNode.querySelectorAll('.value-label');
      this.thumbWidth = parseInt(this.sliderThumbNode.getAttribute('width'));
      this.thumbHeight = parseInt(this.sliderThumbNode.getAttribute('height'));
      this.focusHeight = parseInt(this.sliderFocusNode.getAttribute('height'));
      this.focusWidth = parseInt(this.sliderFocusNode.getAttribute('width'));
      this.thumbY = this.railY + this.railHeight / 2 - this.thumbHeight / 2;
      this.sliderThumbNode.setAttribute('y', this.thumbY);
      this.focusY = this.railY + this.railHeight / 2 - this.focusHeight / 2;
      this.sliderFocusNode.setAttribute('y', this.focusY);
  
  
      this.highMarker = domNode.querySelector('#id-higher-marker');
      this.highMarkerValueNode = this.highMarker.querySelector('.value');
      this.highMarkerFocusNode = this.highMarker.querySelector('.focus-ring');
      this.highMarkerThumbNode = this.highMarker.querySelector('.thumb');
      //this.valueLabelNodes = domNode.querySelectorAll('.value-label');
      this.highMarker.thumbWidth = parseInt(this.highMarkerThumbNode.getAttribute('width'));
      this.highMarker.thumbHeight = parseInt(this.highMarkerThumbNode.getAttribute('height'));
      this.highMarker.focusHeight = parseInt(this.highMarkerFocusNode.getAttribute('height'));
      this.highMarker.focusWidth = parseInt(this.highMarkerFocusNode.getAttribute('width'));
      this.highMarker.thumbY = this.railY + this.railHeight / 2 - this.highMarker.thumbHeight / 2;
      this.highMarkerThumbNode.setAttribute('y', this.highMarker.thumbY);
      this.highMarker.focusY = this.railY + this.railHeight / 2 - this.highMarker.focusHeight / 2;
      this.highMarkerFocusNode.setAttribute('y', this.highMarker.focusY);
  
  
  
      
      // define possible slider positions
      // bind a pointermove event handler to move pointer
      this.svgNode.addEventListener('pointermove', this.onPointerMove.bind(this));
      //this.svgNode.addEventListener('pointermove', this.onMarkerMove.bind(this));
  
      //this.svgNode.addEventListener('click', this.onRailClick.bind(this));
      this.sliderNode.addEventListener('keydown',  this.onSliderKeydown.bind(this)  );
      this.sliderNode.addEventListener('pointerdown',  this.onSliderPointerDown.bind(this));
  
      this.highMarker.addEventListener('keydown',  this.onHighMarkerKeydown.bind(this)  );
      this.highMarker.addEventListener('pointerdown',  this.onSliderPointerDown.bind(this));
  
      
  
      // bind a pointerup event handler to stop tracking pointer movements
      document.addEventListener('pointerup', this.onPointerUp.bind(this));
  
      this.sliderNode.addEventListener('focus', this.onSliderFocus.bind(this));
      this.sliderNode.addEventListener('blur', this.onSliderBlur.bind(this));
  
      this.highMarker.addEventListener('focus', this.onSliderFocus.bind(this));
      this.highMarker.addEventListener('blur', this.onSliderBlur.bind(this));
  
      let deltaPosition = this.railWidth / (this.valueLabelNodes.length - 1);
  
      let position = this.railX;
  
      this.positions = [];
      this.textValues = [];
  
      let maxTextWidth = this.getWidthFromLabelText();
      let textHeight = this.getHeightFromLabelText();
  
      for (let i = 0; i < this.valueLabelNodes.length; i++) {
        let valueLabelNode = this.valueLabelNodes[i];
  
        let textNode = valueLabelNode.querySelector('text');
  
        let w = maxTextWidth + 2;
        let x = position - w / 2;
        let y = this.thumbY + this.thumbHeight;
  
        x = x + (maxTextWidth - textNode.getBoundingClientRect().width) / 2;
        if( x < 0 ) x =0;
        y = y + textHeight;
  
        textNode.setAttribute('x', x);
        textNode.setAttribute('y', y);
  
        this.textValues.push(valueLabelNode.getAttribute('data-value'));
  
        this.positions.push(position);
        position += deltaPosition;
      }
  
      // temporarily show slider value to allow width calc onload
      this.sliderValueNode.setAttribute('style', 'display: block');
      this.moveSliderTo(this.getValue());
      this.sliderValueNode.removeAttribute('style');
  
      this.highMarkerValueNode.setAttribute('style', 'display: block');
      this.moveHighMarkerTo(this.getHighMarkerValue());
      this.highMarkerValueNode.removeAttribute('style');
  
      
      // Include total time in aria-valuetext when loaded
      this.sliderNode.setAttribute(
        'aria-valuetext',
        ( this.getTextValueDesc? this.getTextValueDesc(this.getValue()): this.getValue() )
      );
    }
  
    getHTMLSetup()
    {
      var margin = 80;
      var width = parseInt(this.width);
      var noOfScales = this.noOfScales;
      var increment = (this.maxValue - this.minValue)/noOfScales;
      var ariaDescribedByText = '';
      if(this.ariaDescribedBy.trim() != "" )
          ariaDescribedByText = 'aria-describedby="'+this.ariaDescribedBy+'" ';
  
     var html = `<svg role="none" class="slider-group" width="`+( width+margin)+`" height="120">
      <rect class="rail" x="25" y="50" width="`+ ( width) +`" height="8" rx="5" aria-hidden="true"></rect>
      <g  role="slider" tabindex="0" aria-valuemin="`+this.minValue+`" 
      aria-valuenow="`+this.currentValue+`" aria-valuetext="" aria-valuemax="`+this.maxValue+`"  aria-label="set lower range"  `+ariaDescribedByText+` >
      <text class="value" x="0" y="15"></text>
      <rect class="focus-ring" x="0" y="0" width="28" height="60" rx="12"></rect>
      <rect class="thumb" x="0" y="0" width="14" height="48" rx="5"></rect>
      </g>`;
      
      html += `<g id="id-higher-marker" role="slider" tabindex="0" aria-valuemin="`+this.minValue+`" 
      aria-valuenow="`+this.higherRange+`" aria-valuetext="" aria-valuemax="`+this.maxValue+`" aria-label="set higher range" `+ariaDescribedByText+` >
      <text class="value" x="0" y="15"></text>
      <rect class="focus-ring" x="0" y="0" width="28" height="60" rx="12"></rect>
      <rect class="thumb" x="0" y="0" width="14" height="48" rx="5"></rect>
      </g>`;
      
      for(var i = this.minValue; i <= this.maxValue; i= i + increment)
      {
          var textVal = i;
          if(this.getTextValue) textVal = this.getTextValue(i);
          html += '<g class="value-label" data-value="'+ i +'"><text x="0" y="0">'+ textVal +'</text> </g>';
      }        
      return html;          
    }
  
    getWidthFromLabelText() {
      let width = 0;
      for (let i = 0; i < this.valueLabelNodes.length; i++) {
        let textNode = this.valueLabelNodes[i].querySelector('text');
        if (textNode) {
          width = Math.max(width, textNode.getBoundingClientRect().width);
        }
      }
      return width;
    }
  
    getHeightFromLabelText() {
      let height = 0;
      let textNode = this.valueLabelNodes[0].querySelector('text');
      if (textNode) {
        height = textNode.getBoundingClientRect().height;
      }
      return height;
    }
  
    // Get point in global SVG space
    getSVGPoint(event) {
      this.svgPoint.x = event.clientX;
      this.svgPoint.y = event.clientY;
      return this.svgPoint.matrixTransform(this.svgNode.getScreenCTM().inverse());
    }
  
    getValue() {
      return parseInt(this.sliderNode.getAttribute('aria-valuenow'));
    }
    getValueMin() {
      return parseInt(this.sliderNode.getAttribute('aria-valuemin'));
    }
  
    getValueMax() {
      return parseInt(this.sliderNode.getAttribute('aria-valuemax'));
    }
  
    getHighMarkerValue() {
      return parseInt(this.highMarker.getAttribute('aria-valuenow'));
    }
    getHighMarkerMin() {
      return parseInt(this.highMarker.getAttribute('aria-valuemin'));
    }
  
    getHighMarkerMax() {
      return parseInt(this.highMarker.getAttribute('aria-valuemax'));
    }
  
    
  
    isInRange(value) {
      let valueMin = this.getValueMin();
      let valueMax = this.getValueMax();
      return value <= valueMax && value >= valueMin;
    }
  
    
  
    moveSliderTo(value) {
      let valueMax, valueMin, pos, width;
  
      valueMin = this.getValueMin();
      valueMax = this.getValueMax();
  
      value = Math.min(Math.max(value, valueMin), valueMax);
      if(value >= this.getHighMarkerValue())
          return;
        
      this.sliderNode.setAttribute('aria-valuenow', value);
  
      this.sliderValueNode.textContent = ( this.getTextValue? this.getTextValue(value): value );
  
      width = this.sliderValueNode.getBoundingClientRect().width;
      
      this.sliderNode.setAttribute(
        'aria-valuetext',
        ( this.getTextValueDesc? this.getTextValueDesc(this.getValue()): this.getValue() )
      );
  
      pos =
        this.railX +
        Math.round(((value - valueMin) * this.railWidth) / (valueMax - valueMin));
    
      // move the SVG focus ring and thumb elements
      this.sliderFocusNode.setAttribute('x', pos - this.focusWidth / 2);
      this.sliderThumbNode.setAttribute('x', pos - this.thumbWidth / 2);
      this.sliderValueNode.setAttribute('x', ( (pos - width) > 0 ? (pos - width)  : 0 ));
    }
  
    moveHighMarkerTo(value) {
      let valueMax, valueMin, pos, width;
  
      valueMin = this.getHighMarkerMin();
      valueMax = this.getHighMarkerMax();
  
      value = Math.min(Math.max(value, valueMin), valueMax);
      if(value <= this.getValue())
          return;
      
  
      this.highMarker.setAttribute('aria-valuenow', value);
  
      this.highMarkerValueNode.textContent = ( this.getTextValue? this.getTextValue(value): value );
  
      width = this.highMarkerValueNode.getBoundingClientRect().width;
  
      this.highMarker.setAttribute(
        'aria-valuetext',
        ( this.getTextValueDesc? this.getTextValueDesc(this.getHighMarkerValue()): this.getHighMarkerValue() )
      );
  
      pos =
        this.railX +
        Math.round(((value - valueMin) * this.railWidth) / (valueMax - valueMin));
  
      // move the SVG focus ring and thumb elements
      this.highMarkerFocusNode.setAttribute('x', pos - this.highMarker.focusWidth / 2);
      this.highMarkerThumbNode.setAttribute('x', pos - this.highMarker.thumbWidth / 2);
      this.highMarkerValueNode.setAttribute('x', pos - width / 2);
    }
  
  
    onSliderKeydown(event) {
      var flag = false;
      var value = this.getValue();
      var valueMin = this.getValueMin();
      var valueMax = this.getValueMax();
  
      switch (event.key) {
        case 'ArrowLeft':
        case 'ArrowDown':
          this.moveSliderTo(value - 1);
          flag = true;
          break;
  
        case 'ArrowRight':
        case 'ArrowUp':
          this.moveSliderTo(value + 1);
          flag = true;
          break;
  
        case 'PageDown':
          this.moveSliderTo(value - 15);
          flag = true;
          break;
  
        case 'PageUp':
          this.moveSliderTo(value + 15);
          flag = true;
          break;
  
        case 'Home':
          this.moveSliderTo(valueMin);
          flag = true;
          break;
  
        case 'End':
          this.moveSliderTo(valueMax);
          flag = true;
          break;
  
        default:
          break;
      }
  
      if (flag) {
        event.preventDefault();
        event.stopPropagation();
      }
    }
    onHighMarkerKeydown(event) {
      var flag = false;
      var value = this.getHighMarkerValue();
      var valueMin = this.getHighMarkerMin();
      var valueMax = this.getHighMarkerMax();
  
      switch (event.key) {
        case 'ArrowLeft':
        case 'ArrowDown':
          this.moveHighMarkerTo(value - 1);
          flag = true;
          break;
  
        case 'ArrowRight':
        case 'ArrowUp':
          this.moveHighMarkerTo(value + 1);
          flag = true;
          break;
  
        case 'PageDown':
          this.moveHighMarkerTo(value - 15);
          flag = true;
          break;
  
        case 'PageUp':
          this.moveHighMarkerTo(value + 15);
          flag = true;
          break;
  
        case 'Home':
          this.moveHighMarkerTo(valueMin);
          flag = true;
          break;
  
        case 'End':
          this.moveHighMarkerTo(valueMax);
          flag = true;
          break;
  
        default:
          break;
      }
  
      if (flag) {
        event.preventDefault();
        event.stopPropagation();
      }
    }
    
  
    onSliderFocus() {
      //this.domNode.classList.add('focus');
      if(this.markerColor != "")
      {
          
         if( document.activeElement == this.sliderNode)
         {
              this.sliderNode.querySelector(".value").style.color = this.markerColor;
              this.sliderNode.querySelector(".thumb").style.color = this.markerColor;
              this.sliderNode.querySelector(".focus-ring").style.color = this.markerColor;
         }
         if( document.activeElement == this.highMarker)
         {
              this.highMarker.querySelector(".value").style.color = this.markerColor;
              this.highMarker.querySelector(".thumb").style.color = this.markerColor;
              this.highMarker.querySelector(".focus-ring").style.color = this.markerColor;
         }
      }
    }
  
    onSliderBlur() {
      this.sliderNode.setAttribute(
        'aria-valuetext',
        ( this.getTextValueDesc? this.getTextValueDesc(this.getValue()): this.getValue() )
      );
      if(this.markerColor != "")
      {
          
          this.sliderNode.querySelector(".value").style.color = "";
          this.sliderNode.querySelector(".thumb").style.color = "";
          this.sliderNode.querySelector(".focus-ring").style.color = "";
      
          this.highMarker.querySelector(".value").style.color = "";
          this.highMarker.querySelector(".thumb").style.color = "";
          this.highMarker.querySelector(".focus-ring").style.color = "";
         
      }
      
    }
  
    onRailClick(event) {
      var x = this.getSVGPoint(event).x;
      var min = this.getValueMin();
      var max = this.getValueMax();
      var diffX = x - this.railX;
      var value = Math.round((diffX * (max - min)) / this.railWidth);
      this.moveSliderTo(value);
  
      event.preventDefault();
      event.stopPropagation();
      // Set focus to the clicked handle
      this.sliderNode.focus();
    }
  
    onSliderPointerDown(event) {
      this.isMoving = true;
      this.movingSlider = event.target;
      event.preventDefault();
      event.stopPropagation();
      // Set focus to the clicked handle
      if(event.target == this.sliderThumbNode )
          this.sliderNode.focus();
      if(event.target == this.highMarkerThumbNode )
          this.highMarker.focus();
  
    }
  
    onPointerMove(event) {
      if (this.isMoving) {
        var x = this.getSVGPoint(event).x;
        var min = this.getValueMin();
        var max = this.getValueMax();
        if(event.target == this.highMarkerThumbNode )
        {
              min = this.getHighMarkerMin();
              max = this.getHighMarkerMax();
        }
        var diffX = x - this.railX;
        var value = Math.round((diffX * (max - min)) / this.railWidth);
        if(this.movingSlider == this.sliderThumbNode )
          this.moveSliderTo(min + value);
        if(this.movingSlider == this.highMarkerThumbNode )
          this.moveHighMarkerTo(min + value);
  
        event.preventDefault();
        event.stopPropagation();
      }
    }
    onMarkerMove(event) {
      if (this.isMoving) {
        var x = this.getSVGPoint(event).x;
        var min = this.getHighMarkerMin();
        var max = this.getHighMarkerMax();
        var diffX = x - this.railX;
        var value = Math.round((diffX * (max - min)) / this.railWidth);
        this.moveHighMarkerTo(min+value);
        event.preventDefault();
        event.stopPropagation();
      }
    }
  
    onPointerUp() {
      this.isMoving = false;
      this.movingSlider = null;
    }
  }
  
  var widgets = {};
  window.addEventListener('load', function () {
    createMediaseekWidget();
    createHousePriceWidget();
    createMonthWidget();
  });
  
  window.addEventListener('resize', function () {
    createMediaseekWidget();
    createHousePriceWidget();
    createMonthWidget();
  });
  
  
  
  
  function createMediaseekWidget()
  {
      let slider = document.querySelector('#media-seek-slider');
      var maxWidth = getMaxWidth();
      var options = {};
      options.sliderColor = "#990c0c";
      options.markerColor = "#225f5f";
      options["getTextValue"] = getMinutesSeconds;
      options["getTextValueDesc"] = getMinutesSecondsText;
      options.noOfScales = 5;
      options.width = ( maxWidth > 600 ? "400": "270" );
      options.ariaDescribedBy = "slider-seek-label";
      widgets["mediaSeek"] = new SliderMultirange(slider,0,300,90,180, options);
  }
  function createHousePriceWidget()
  {
      let slider = document.querySelector('#house-price-slider');
      var maxWidth = getMaxWidth();
      var options = {};
      options.sliderColor = "#333b88";
      options.noOfScales = 5;
      if( maxWidth < 1200 ) options.noOfScales = 4;
      options.width = ( maxWidth > 800 ? maxWidth * .6: maxWidth * .72);
      var formatNumber = function(num){
          if (maxWidth > 1200 ) 
          return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(num);
          return ("$ "+(num/1000)+"K");
      };
      options["getTextValue"] = formatNumber;
      options["getTextValueDesc"] = formatNumber;
      
      var lowRangeVal = 200000;
      var highRangeVal = 600000;
      if(typeof widgets["housePrice"] == "object")
      {
          lowRangeVal = widgets["housePrice"].getValue();
          highRangeVal = widgets["housePrice"].getHighMarkerValue();
      }
      options.ariaDescribedBy = "slider-house-label";
      widgets["housePrice"] = new SliderMultirange(slider,0,1000000,lowRangeVal,highRangeVal, options);
  }
  function getMaxWidth()
  {
      var maxWidth = screen.width;
      if( maxWidth > window.innerWidth ) maxWidth = window.innerWidth;
      return maxWidth;
  }
  function createMonthWidget()
  {
      let slider = document.querySelector('#month-slider');
      var maxWidth = getMaxWidth();
      var formatNumber = function(num){  
          const date = new Date(2020, num, 10);  // 2009-11-10
          const month = date.toLocaleString('default', { month: 'long' });
          return month.substring(0,3);
      };
      var options = {};
      //options.sliderColor = "#333b88";
      options["getTextValue"] = formatNumber;
      options["getTextValueDesc"] = formatNumber;
      options.noOfScales = 11;
      if( maxWidth < 1000 ) options.noOfScales = 4;
      options.width = ( maxWidth > 800 ? maxWidth * .6: maxWidth * .72);
      options.ariaDescribedBy = "slider-month-label";
      var lowRangeVal = 1;
      var highRangeVal = 8;
      if(typeof widgets["monthRange"] == "object")
      {
          lowRangeVal = widgets["monthRange"].getValue();
          highRangeVal = widgets["monthRange"].getHighMarkerValue();
      }
      widgets["monthRange"] = new SliderMultirange(slider,0,11,lowRangeVal, highRangeVal, options);
  }
  
  function getMinutesSecondsText(value)
  {
      var minutes =  Math.floor( value / 60);
      var seconds =  ( value % 60);
      var valueText = "";
      if(minutes == 1 ) valueText += minutes + " minute ";
      if(minutes > 1 ) valueText += minutes + " minutes ";
      if(seconds == 1 ) valueText += seconds + " second ";
      if(seconds > 1 ) valueText += seconds + " seconds ";
      return valueText;
  }
  
  function getMinutesSeconds(value)
  {
      var minutes =  Math.floor( value / 60);
      var seconds =  ( value % 60);
      if( seconds < 10 ) seconds = "0"+seconds;
      return minutes +":"+ seconds;
  }
  
  
  function htmlSliderChanged(event)
  {
      var slider = event.target;
      var minutes =  Math.floor( slider.value / 60);
      var seconds =  ( slider.value % 60);
      var valueText = getMinutesSecondsText(slider.value);
      if( seconds < 10 ) seconds = "0"+seconds;
      document.getElementById("current-value").innerHTML = minutes +":"+ seconds;
      document.getElementById("seek-input").setAttribute("aria-valuetext", valueText);
  }
  

CSS Source Code

slider-seek .label {
    font-weight: bold;
    font-size: 125%;
  }
  
  .slider-seek svg {
    forced-color-adjust: auto;
  }
  
  .slider-seek text {
    font-weight: bold;
    fill: currentcolor;
    font-family: sans-serif;
  }
  
  .slider-seek {
    margin-top: 1em;
    padding: 6px;
    color: black;
  }
  
  .slider-slider .value {
    position: relative;
    top: 20px;
    height: 1.5em;
    font-size: 80%;
  }
  
  .slider-seek .temp-value {
    padding-left: 24px;
    font-size: 200%;
  }
  
  .slider-seek .rail {
    stroke: currentcolor;
    stroke-width: 2px;
    fill: currentcolor;
    fill-opacity: 0.25;
  }
  
  .slider-seek .thumb {
    stroke-width: 0;
    fill: currentcolor;
  }
  
  .slider-seek .focus-ring {
    stroke: currentcolor;
    stroke-opacity: 0;
    fill-opacity: 0;
    stroke-width: 3px;
    display: none;
  }
  
  .slider-seek .slider-group {
    touch-action: pan-y;
  }
  
  .slider-seek .slider-group .value {
    display: none;
  }
  
  /* Focus and hover styling */
  
  .slider-seek.focus [role="slider"] {
    color: #005a9c;
  }
  
  .slider-seek [role="slider"]:focus {
    outline: none;
  }
  
  .slider-seek [role="slider"]:focus .focus-ring {
    display: block;
    stroke-opacity: 1;
  }
  
  .slider-seek [role="slider"]:focus .value {
    display: block;
  }
  
  /*
This component has been adapted from an example provided by the W3C, in accordance with the W3C Software and Document License https://www.w3.org/copyright/software-license-2023/
*/  

Copy and Paste Full Page Example