15

I would like to customize input type range like this.

enter image description here

I tried to use the following code to change the thumb and as below but selected range color is not changed.

HTML:

<input type="range" />

CSS:

input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  height: 20px;
  width: 20px;
  border-radius: 10px;
  background: red;
  cursor: pointer;
  border: none;
  margin-top: -8px;
}

input[type=range] {
  -webkit-appearance: none;
  outline: 0;
  margin: 0;
  padding: 0;
  height: 30px;
  width: 100%;
}

Could anybody please suggest me to change the selected runnable track background color?

Harry
  • 87,580
  • 25
  • 202
  • 214
Ponmalar
  • 6,871
  • 10
  • 50
  • 80
  • A pure CSS solution can be found here: http://stackoverflow.com/a/38163892 In particular, it uses box-shadow to emulate the split in colors on the track in Chrome. – CurTailed Jan 16 '17 at 17:56

2 Answers2

9

Interesting approach being used by the Ionic framework for styling the range input track with just CSS. They are adding a ::before pseudo-element to the ::-webkit-slider-thumb, making it as wide as possible and then positioning it on top of the track. (I couldn't get border-radius to work with it.)

input[type='range'] {
  width: 210px;
  height: 30px;
  overflow: hidden;
  cursor: pointer;
}
input[type='range'],
input[type='range']::-webkit-slider-runnable-track,
input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
}
input[type='range']::-webkit-slider-runnable-track {
  width: 200px;
  height: 10px;
  background: #AAA;
}
input[type='range']::-webkit-slider-thumb {
  position: relative;
  height: 30px;
  width: 30px;
  margin-top: -10px;
  background: steelblue;
  border-radius: 50%;
  border: 2px solid white;
}
input[type='range']::-webkit-slider-thumb::before {
  position: absolute;
  content: '';
  height: 10px; /* equal to height of runnable track */
  width: 500px; /* make this bigger than the widest range input element */
  left: -502px; /* this should be -2px - width */
  top: 8px; /* don't change this */
  background: #777;
}
<div class="container">
  <input type="range" min="0" max="100" value="10" />
</div>

As far as I am aware there is no pure CSS way to do this for Webkit powered browsers (and FF). IE provides a way to style the two portions of the track using -ms-fill-lower and -ms-fill-upper but there are no equivalents in WebKit and hence JavaScript will be needed.

You could use a linear-gradient as background image for the runnable track and then change the background-size using JavaScript to achieve the required effect. As question is specific to Webkit, the snippet provided currently works only in Webkit powered browsers. This method does allow us to add a border-radius to the track.

This snippet was adapted from Ana Tudor's CodePen Demo. That demo has ways to make it work in other browsers also.

window.onload = function() {
  var input = document.querySelector('input[type=range]'),
    style_el = document.createElement('style'),
    styles = [],
    track_sel = ['::-webkit-slider-runnable-track'];
  document.body.appendChild(style_el);

  styles.push('');

  input.addEventListener('input', function() {
    var min = this.min || 0,
      max = this.max || 100,
      c_style, u, edge_w, val, str = '';

    this.setAttribute('value', this.value);

    val = this.value + '% 100%';
    str += 'input[type="range"]' + track_sel[0] + '{background-size:' + val + '}';

    styles[0] = str;
    style_el.textContent = styles.join('');
  }, false);
}
input[type='range'] {
  width: 210px;
  height: 50px;
  cursor: pointer;
}
input[type='range'],
input[type='range']::-webkit-slider-runnable-track,
input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
}
input[type='range']::-webkit-slider-runnable-track {
  width: 200px;
  height: 10px;
  background: linear-gradient(to right, #777, #777), #AAA;
  background-size: 10% 100%;
  background-repeat: no-repeat;
  border-radius: 5px;
}
input[type='range']::-webkit-slider-thumb {
  height: 30px;
  width: 30px;
  margin-top: -10px;
  background: steelblue;
  border-radius: 50%;
  border: 2px solid white;
}
<div class="container">
  <input type="range" min="0" max="100" value="10" />
</div>
Harry
  • 87,580
  • 25
  • 202
  • 214
  • Yes using JS we have a multiple way. simplest one is calling $('input:range').css('background', 'linear-gradient(to right, red, #005555 ' +val + '%, blue)') with oninput method. But i need to do this with CSS only. Like input::-webkit-slider-thumb:after and before methods – Ponmalar Jan 18 '16 at 09:18
  • Ah sorry, I didn't note the *pure CSS* bit in the title. But there is not a way to do this with pure CSS for Webkit powered browsers. IE, I think has an upper and a lower to style the two portions of the track but Webkit (and FF) doesn't have that. – Harry Jan 18 '16 at 09:19
  • 2
    have you checked ionic range control: http://ionicframework.com/docs/components/#range. It has been implemented only through CSS – Ponmalar Jan 18 '16 at 09:27
  • @Ponmalar: I will have a look at it but I guess they must be using JS (for cross browser). You may want to have a look at this article also - http://www.hongkiat.com/blog/html5-range-slider-style/ – Harry Jan 18 '16 at 09:29
  • @Ponmalar: Interesting but yes Ionic is using pure CSS and I've added it to the answer now. Thanks for that link, that's something new I learnt today :) – Harry Jan 18 '16 at 09:43
  • 1
    Why this is not working with safari browser? just run this code snippet in Safari, you can reproduce the issue. – Ponmalar Jan 22 '16 at 10:36
  • @Ponmalar: I don't have Safari so can't test with it but the properties that are added are not browser specific, the selectors are browser specific but since Safari uses WebKit I assume there should be no problems with it either (but based on your comment, it seems like there is). – Harry Jan 22 '16 at 11:18
6

I got a solution that does not use a style tag but css variable:

const input = document.querySelector("input");

function setBackgroundSize(input) {
  input.style.setProperty("--background-size", `${getBackgroundSize(input)}%`);
}

setBackgroundSize(input);

input.addEventListener("input", () => setBackgroundSize(input));

function getBackgroundSize(input) {
  const min = +input.min || 0;
  const max = +input.max || 100;
  const value = +input.value;

  const size = (value - min) / (max - min) * 100;

  return size;
}
input[type='range'] {
  width: 400px;
}

input[type='range'],
input[type='range']::-webkit-slider-runnable-track,
input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
}

input[type='range']::-webkit-slider-runnable-track {
  height: 3px;
  background: linear-gradient(to right, #293043, #293043), #D7D7D7;
  background-size: var(--background-size, 0%) 100%;
  background-repeat: no-repeat;
  border-radius: 5px;

}

input[type='range']::-webkit-slider-thumb {
  width: 15px;
  height: 15px;
  cursor: pointer;
  background: #293043;
  border: solid white 1px;
  border-radius: 50%;
  margin-top: -6px;
  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.4);
}

/** FF*/

input[type="range"]::-moz-range-progress {
  background-color: #293043;
  border-radius: 5px;
}

input[type="range"]::-moz-range-track {
  background-color: #D7D7D7;
  border-radius: 5px;
}

input[type="range"]::-moz-range-thumb {
  width: 15px;
  height: 15px;
  cursor: pointer;
  background: #293043;
  border: solid white 1px;
  border-radius: 50%;
  margin-top: -6px;
  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.4);
}
<input type="range" min="5" max="95" step="1" value="15">

This works on Chrome and Firefox :).

Kurtil
  • 61
  • 1
  • 2