How To Implement Numeric Range Slider Input With CSS3

This example will show you how to use pure css3 to implement some numeric range slider input component on a web page. Below is the demo video.

1. Numeric Range Slider Input With CSS3 Source Files.

This example contains below files.

./
├── css
│   ├── normalize.min.css
│   └── style.css
└── index.html

1.1 index.html.

<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>How To Implement Numeric Range Slider Input With CSS3</title>

<link rel="stylesheet" href="css/normalize.min.css">
<link rel="stylesheet" href="css/style.css">

</head>
<body>

<div class="range" style='--min:0; --max:1000; --value:170; --text-value:"170"; --thumb-size: 22px; --track-height: calc(var(--thumb-size)/3);'>
  <input type="range" min="0" max="1000" value="170" oninput="this.parentNode.style.setProperty('--value',this.value); this.parentNode.style.setProperty('--text-value', JSON.stringify(this.value))">
  <output></output>
  <div class='range__progress'></div>
</div>

<div class="range" style='--step:5; --min:20; --max:100; --value:40; --text-value:"40"; --tickEvery:4; --fill-color:linear-gradient(to right, white, var(--primaryColor));'>
  <input type="range" min="20" max="100" step="5" value="40" oninput="this.parentNode.style.setProperty('--value',this.value); this.parentNode.style.setProperty('--text-value', JSON.stringify(this.value))">
  <output></output>
  <div class='range__progress'></div>
</div>

<div class="range" data-ticks-position='top' style='--step:.1; --min:-1; --max:1; --value:.5; --text-value:"0.5"; --primaryColor:gold; --fill-color:linear-gradient(to right, TOMATO 50%, MEDIUMSEAGREEN 50%); --value-active-color:unset;'>
  <input type="range" min="-1" max="1" step=".1" value=".5" oninput="this.parentNode.style.setProperty('--value',this.value); this.parentNode.style.setProperty('--text-value', JSON.stringify(this.value))">
  <output></output>
  <div class='range__progress'></div>
</div>

<label dir="rtl">
  <xmp><label dir=rtl></xmp>
  <div class="range" style='--min:0; --max:1000; --value:170; --text-value:"170"; --thumb-size:22px; --track-height:calc(var(--thumb-size)/3);'>
    <input type="range" min="0" max="1000" value="170" oninput="this.parentNode.style.setProperty('--value',this.value); this.parentNode.style.setProperty('--text-value', JSON.stringify(this.value))">
    <output></output>
    <div class='range__progress'></div>
  </div>
</label>

</body>
</html>

1.2 css/style.css.

body {
  height: 100vh;
  display: grid;
  place-content: center;
  place-items: center;
  gap: 10%;
}

a {
  position: fixed;
  top: 1em;
  left: 50%;
  -webkit-transform: translateX(-50%);
          transform: translateX(-50%);
  display: inline-block;
  width: 200px;
}
a > img {
  display: inherit;
  width: 100%;
}

.controller {
  display: -webkit-box;
  display: flex;
  width: 90vw;
  border-bottom: 1px solid #DDD;
  padding: 2em 0;
}
.controller .range {
  width: 100%;
  min-width: 100px;
}
.controller label {
  -webkit-box-flex: 1;
          flex: 1;
}
.controller label:not(:last-child) {
  padding-right: 1em;
}
.controller label > span {
  display: block;
  margin-bottom: 1em;
}

body > label > xmp {
  text-align: center;
}
body > label > span {
  margin-left: 1em;
}

.range {
  width: 50vw;
  min-width: 200px;
}

.range {
  --primaryColor: #0366D6;
  --value-active-color: white;
  --value-background: white;
  --value-font: 700 12px/1 Arial;
  --progress-color: #EEE;
  --progress-shadow: 2px 2px 4px rgba(0,0,0, .1) inset;
  --fill-color: var(--primaryColor);
  --thumb-size: 16px;
  --track-height: calc(var(--thumb-size)/2);
  --thumb-shadow: 0 0 3px rgba(0,0,0,.2);
  --ticks-thickness: 1px;
  --ticks-height: 5px;
  --ticks-color: silver;
  --step: 1;
  --ticks-count: (var(--max) - var(--min)) / var(--step);
  --maxTicksAllowed: 30;
  --too-many-ticks: Min(1, Max(var(--ticks-count) - var(--maxTicksAllowed), 0));
  --x-step: Max( var(--step), var(--too-many-ticks) * (var(--max) - var(--min)) );
  --tickInterval: 100/((var(--max) - var(--min)) / var(--step)) * var(--tickEvery, 1);
  --tickIntervalPerc: calc((100% - var(--thumb-size))/( (var(--max) - var(--min)) / var(--x-step) ) * var(--tickEvery, 1));
  --completed: calc((var(--value) - var(--min) ) / (var(--max) - var(--min)) * 100);
  --LTR: 1;
  display: inline-block;
  height: Max(var(--track-height), var(--thumb-size));
  background: linear-gradient(to right, var(--ticks-color) var(--ticks-thickness), transparent 1px) repeat-x;
  background-size: var(--tickIntervalPerc) var(--ticks-height);
  background-position-x: calc(var(--thumb-size)/2);
  background-position-y: var(--flip-y, bottom);
  position: relative;
  z-index: 1;
  padding-bottom: var(--flip-y, var(--ticks-height));
  padding-top: calc(var(--flip-y) * var(--ticks-height));
}
[dir="rtl"] .range {
  --LTR: -1;
}
.range[data-ticks-position='top'] {
  --flip-y: 1;
}
.range::before, .range::after {
  --offset: calc(var(--thumb-size)/2);
  content: counter(x);
  font: 12px Arial;
  position: absolute;
  bottom: var(--flip-y, -2.5ch);
  top: calc(-2.5ch * var(--flip-y));
  opacity: var(--min-max-opacity, 0.5);
  pointer-events: none;
}
.range::before {
  counter-reset: x var(--min);
  left: var(--offset);
  -webkit-transform: translateX(Calc(-50% * var(--LTR)));
          transform: translateX(Calc(-50% * var(--LTR)));
}
[dir='rtl'] .range::before {
  left: auto;
  right: var(--offset);
}
.range::after {
  counter-reset: x var(--max);
  right: var(--offset);
  -webkit-transform: translateX(Calc(50% * var(--LTR)));
          transform: translateX(Calc(50% * var(--LTR)));
}
[dir='rtl'] .range::after {
  right: auto;
  left: var(--offset);
}
.range__progress {
  position: absolute;
  left: 0;
  top: calc(50% - var(--ticks-height)/2);
  -webkit-transform: var(--flip-y, translateY(-50%) translateZ(0));
          transform: var(--flip-y, translateY(-50%) translateZ(0));
  width: 100%;
  height: calc(var(--track-height));
  pointer-events: none;
  z-index: -1;
  box-shadow: var(--progress-shadow);
  border-radius: 20px;
  background: var(--fill-color, white);
}
.range__progress::after {
  content: '';
  display: block;
  margin-left: auto;
  margin-right: -1px;
  width: calc((100% - var(--completed) * 1%) + (var(--completed)/100) * var(--thumb-size));
  height: 100%;
  background: var(--progress-color, #EEE);
  box-shadow: inherit;
  border-radius: 0 20px 20px 0;
}
[dir="rtl"] .range__progress::after {
  margin-right: auto;
  margin-left: -1px;
  border-radius: 20px 0 0 20px;
}
.range > input {
  -webkit-appearance: none;
  width: 100%;
  height: var(--thumb-size);
  margin: 0;
  cursor: -webkit-grab;
  cursor: grab;
  outline: none;
  background: none;
}
.range > input::-webkit-slider-thumb {
  -webkit-appearance: none;
          appearance: none;
  height: var(--thumb-size);
  width: var(--thumb-size);
  border-radius: 50%;
  background: var(--thumb-color, white);
  border: 1px solid silver;
  box-shadow: var(--inner-shadow, 0 0), var(--thumb-shadow);
}
.range > input::-moz-slider-thumb {
  -moz-appearance: none;
       appearance: none;
  height: var(--thumb-size);
  width: var(--thumb-size);
  border-radius: 50%;
  background: var(--thumb-color, white);
  border: 1px solid silver;
  box-shadow: var(--inner-shadow, 0 0), var(--thumb-shadow);
}
.range > input::-ms-thumb {
  appearance: none;
  height: var(--thumb-size);
  width: var(--thumb-size);
  border-radius: 50%;
  background: var(--thumb-color, white);
  border: 1px solid silver;
  box-shadow: var(--inner-shadow, 0 0), var(--thumb-shadow);
}
.range > input:active {
  cursor: -webkit-grabbing;
  cursor: grabbing;
  --thumb-color: var(--fill-color);
  --inner-shadow: 0 0 0 calc(var(--thumb-size)/4) inset white;
}
.range > input:active + output {
  -webkit-transition: 0s;
  transition: 0s;
}
.range > input:hover + output {
  --value-background: var(--primaryColor);
  color: var(--value-active-color);
  -webkit-transform: translate(var(--x-offset), 0);
          transform: translate(var(--x-offset), 0);
  box-shadow: 0 0 0 3px var(--value-background);
}
.range > output {
  --x-offset: calc(var(--completed) * -1% * var(--LTR));
  --pos: calc(((var(--value) - var(--min))/(var(--max) - var(--min))) * 100%);
  pointer-events: none;
  position: absolute;
  z-index: 5;
  background: var(--value-background);
  border-radius: 10px;
  padding: 0 4px;
  top: -3ch;
  left: var(--pos);
  -webkit-transform: translate(var(--x-offset), 6px);
          transform: translate(var(--x-offset), 6px);
  -webkit-transition: all .12s ease-out, left 0s, top 0s;
  transition: all .12s ease-out, left 0s, top 0s;
}
[dir='rtl'] .range > output {
  left: auto;
  right: var(--pos);
}
.range > output::after {
  content: var(--text-value);
  font: var(--value-font);
}

1.3 css/normalize.min.css.

button,hr,input{overflow:visible}audio,canvas,progress,video{display:inline-block}progress,sub,sup{vertical-align:baseline}html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} menu,article,aside,details,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{}button,select{text-transform:none}[type=submit], [type=reset],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}[hidden],template{display:none}/*# sourceMappingURL=normalize.min.css.map */

Below is the video demo.

https://youtu.be/61Iek-Ox_H4

Reference

  1. https://necolas.github.io/normalize.css/

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.