6

I'm making a site that includes a range input slider. I would like the slider thumb to change colour according to the value of the slider.

For example, if the value was 0, the thumb colour would be rgb(255, 0, 0);, if it were 100, the colour would be rgb(0, 255, 0);, and the thumb would change colour.

To be clear, I don't want something like this:

if slider_value <= 29:
  thumb_color = rgb(255, 0, 0)
else if slider_value >= 30 && slider_value <= 69:
  thumb_color = rgb(255, 255, 0)
else 
  thumb_color = rgb(0, 255, 0)

Here's the code I have so far:

.slider {
  width: 60%;
  margin: 50px auto;
  -webkit-appearance: none;
  height: 8px;
  border-radius: 4px;
  margin-bottom: 15px;
  background-color: rgb(200, 200, 200);
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 10px;
  background-color: rgb(255, 120, 0);
  overflow: visible;
  cursor: pointer;
}

.slidecontainer {
  transform: translateY(-10px);
}
<div class="slidecontainer" align="center">
  <input type="range" min="0" max="100" value="50" class="slider" name="rangeInput">
</div>

Please bear in mind that I know very little Javascript, however I know many other programming languages and I know enough to understand most Javascript code. So please try to make it as simple as is possible.

How would I be able to do this effectively?

TylerH
  • 20,799
  • 66
  • 75
  • 101
notsoscottishscot
  • 350
  • 1
  • 4
  • 11
  • @04FS I would like the slider thumb to change colour as you move it, with the "0" value being red and the "100" value being green. Sorry for not clarifying, I meant I do not want the colour to be an instant change (with 29 being red and 30 being orange), I would like it to gradient through between the two colours. – notsoscottishscot Jul 02 '19 at 12:00
  • And what have your research efforts turned up so far? “Fading” between colors automatically is not an easy thing to achieve in RGB, so you might want to switch to the HSL color model first of all … – 04FS Jul 02 '19 at 12:03
  • Also be aware that this is likely to be performance intensive, changing the color on **every** movement of the slider. – Paulie_D Jul 02 '19 at 12:08
  • I followed the searches that @mplungjan took. And I found a [question](https://stackoverflow.com/questions/15050671/set-background-color-to-value-of-range-input) asking about how to change the background colour with rgb sliders this is a lot closer to what I mean, and what I'm asking for. If you copy the code from the answer into an html script and change the "onchange" to "oninput", this is what I'm looking for, however instead of changing the background, I would like the slider thumb colour to change instead. I hope you understand what I mean. – notsoscottishscot Jul 02 '19 at 12:08
  • I produced a much more simpler solution, see further – Mister Jojo Jul 02 '19 at 17:19

4 Answers4

10

the simplest is to use a variable css. see : https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties

const Slider = document.querySelector('input[name=rangeInput]')

Slider.oninput =_=> Slider.style.setProperty('--SliderColor', `hsl(${Slider.value}, 100%, 50%)`)
.slidecontainer {
  transform: translateY(-10px);
  text-align: center;
}
.slider {
  --SliderColor: hsl(50, 100%, 50%);
  -webkit-appearance: none;
  width: 60%;
  margin: 50px auto;
  height: 8px;
  border-radius: 4px;
  margin-bottom: 15px;
  background-color: rgb(200, 200, 200);
}

/* --------------------------- webkit browsers */
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 10px;
  background-color: var(--SliderColor);
  overflow: visible;
  cursor: pointer;
}
/* -------------------------- Firefox */
.slider::-moz-range-thumb { 
  -moz-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 10px;
  background-color: var(--SliderColor);
  overflow: visible;
  cursor: pointer;
}
.slider::-moz-focus-outer { border: 0; }
/* Remove dotted outline from range input element focused in Firefox */
<div class="slidecontainer">
  <input type="range" min="0" max="100" value="50" class="slider" name="rangeInput">
</div>

[edit]: adding css .slider::-moz-range-thumb (Firefox)

Mister Jojo
  • 20,093
  • 6
  • 21
  • 40
6

You're basically looking for this, right?

const input = document.querySelector('input[type="range"]');
const style = document.createElement('style');
const head = document.head || document.getElementsByTagName('head')[0];
style.type = 'text/css';
head.appendChild(style);
input.oninput = function(e) {
  const cssText = `input.slider::-webkit-slider-thumb, input.slider::-moz-range-thumb {
    background-color: rgb(${255 - 2.55*e.target.value}, ${2.55*e.target.value}, 0);
  }`;
  if (style.styleSheet) {
    style.styleSheet.cssText = cssText;
  } else {
    style.innerHTML = "";
    style.appendChild(document.createTextNode(cssText));
  }
}
.slider {
  width: 60%;
  margin: 50px auto;
  -webkit-appearance: none;
  height: 8px;
  border-radius: 4px;
  margin-bottom: 15px;
  background-color: rgb(200, 200, 200);
}

.slider::-webkit-slider-thumb, 
.slider::-moz-range-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 10px;
  background-color: rgb(128, 128, 0);
  overflow: visible;
  cursor: pointer;
}

.slidecontainer {
  transform: translateY(-10px);
}
  <div class="slidecontainer" align="center">
    <input type="range" min="0" max="100" value="50" class="slider" name="rangeInput" oninput="test">
  </div>

It's a bit trickier as ::webkit-slider-thumb is a pseudo element and (i might be wrong here) i don't think you can target it directly with JavaScript. So what I did was add a <style> tag to <head> and dynamically change its contents based on current input value, in a function triggered on input event.

It's more of a proof of concept, it can be improved by using addEventListener and it probably looks prettier in jQuery. I'll leave all that jazz to you.


Edit: As exaneta's answer points out, you have a range of options when dealing with dynamically changing colors. While I used a simple 255 > 0 for red and 0 > 255 for green in an rgb(), you might want to use exaneta's solution: hsl(${e.target.value}, 100%, 50%).

tao
  • 82,996
  • 16
  • 114
  • 150
  • 1
    dude instead of `e.target.value` you can say `target.value` otherwise you have declared unnecessary variable. – RegarBoy Jul 02 '19 at 12:42
  • @TylerH, OP was not interested in FF, as they only had selectors for Chrome thumbs. Which is why at the time it made no sense to add FF selectors. I did mention in a previous comment one would need to add FF specific thumb selectors to make this proof of concept work in FF. Since it obviously wasn't clear enough, I've updated the answer. – tao Oct 26 '20 at 19:55
  • @tao Fair enough, but the question doesn't say anything about Chrome. `-webkit` vendor prefixes work in almost every browser, including Firefox, depending on the property that follows, hence the expectation. – TylerH Oct 26 '20 at 20:16
  • 1
    @TylerH, I believe mister jojo's answer is more elegant, more concise and quite effective, especially in the context of modern fe frameworks, like Vue, which can use CSS vars scoped to that particular component's instance. – tao Oct 26 '20 at 20:20
5

This is how I would do it: I would create a new <style> element and I woild append this element to the head. Next on input I would write a css rule that would change the thumb's color from red to green using hsl colors. I would make this css rule the text content of the new style element. I hope it helps.

let s = document.createElement("style");
document.head.appendChild(s);
itr.addEventListener("input", () => {
  s.textContent = `.slider::-webkit-slider-thumb{background-color: hsl(${itr.value}, 100%, 50%)}`
})
.slider {
  width: 60%;
  margin: 50px auto;
  -webkit-appearance: none;
  height: 8px;
  border-radius: 4px;
  margin-bottom: 15px;
  background-color: rgb(200, 200, 200);
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 10px;
  background-color: hsl(50, 100%, 50%);
  overflow: visible;
  cursor: pointer;
}

.slidecontainer {
  transform: translateY(-10px);
}
<div class="slidecontainer" align="center">
  <input id="itr" type="range" min="0" max="100" value="50" class="slider" name="rangeInput">
</div>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
enxaneta
  • 31,608
  • 5
  • 29
  • 42
  • Thank you for commenting. If you take a look at my code it looks different than yours. I didn't use your answer - a good answer - by the way. I've upvoted it. – enxaneta Jul 02 '19 at 12:37
  • It uses exactly the same principle. This happens to me also. When it does, I just delete my answer. If my answer holds a point that hasn't been made in the answer that was posted while I was editing mine, I usually suggest it as improvement to the other one. – tao Jul 02 '19 at 12:38
  • Mine is shorter. And I'm using hsl colors. It's different – enxaneta Jul 02 '19 at 12:39
  • 1
    You're correct, HSL looks better and might be a better option when changing color. +1. – tao Jul 02 '19 at 12:46
  • 1
    @RegarBoy because the script can find it. It bothers me too [but works](https://stackoverflow.com/questions/6381425/is-there-a-spec-that-the-id-of-elements-should-be-made-global-variable) – mplungjan Jul 02 '19 at 13:01
  • more here https://stackoverflow.com/questions/25325221/why-dont-we-just-use-element-ids-as-identifiers-in-javascript – mplungjan Jul 02 '19 at 13:03
  • I think this feature of StackOverflow leads new devs into confusion. – RegarBoy Jul 02 '19 at 13:03
  • @RegarBoy - you don't have to. an `id` is made a global variable automatically. – enxaneta Jul 02 '19 at 13:03
  • 1
    @RegarBoy It is not a stackoverflow feature. It is an HTML5 feature – mplungjan Jul 02 '19 at 13:05
  • @mplungjan Then it is even more scary :) went through your link, thanks. – RegarBoy Jul 02 '19 at 13:07
  • 1
    I personally would NEVER use it – mplungjan Jul 02 '19 at 13:08
  • Sorry, something new has come up, for some reason I cannot get it to work, even when isolated in a separate file. Was this done with internal CSS and JS? Please bear in mind that this is a .php file I'm working with. I've put your code into ` – notsoscottishscot Jul 03 '19 at 11:51
  • In your code you don't open the body, and you close the head immediately. Also I use to **put the script in the body just before the closing tag**. I've tested your code in codepen and it works. I'll take a further look at your code and check it in different browsers. – enxaneta Jul 03 '19 at 12:11
  • Could you please provide a link to your version of my code that works perfectly for you? With the changing slider thumb colour? Because (and I am stumped as to why) my code isn't working in chrome, IE, or codepen, The thumb shows as yellow, however, it does not change as it is moved. – notsoscottishscot Jul 03 '19 at 12:19
  • This is the codepen link: https://codepen.io/enxaneta/pen/9561804570da6a82e53cfeaba029758f?editors=0111 – enxaneta Jul 03 '19 at 12:25
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/195948/discussion-between-voidableryzer-and-enxaneta). – notsoscottishscot Jul 03 '19 at 18:19
1

Your post looks similar to this one: .slider::-webkit-slider-thumb needed in javascript

const range = document.getElementById("rangeInput");
var style = document.querySelector('[data="test"]');

range.addEventListener("input", () => {
   const slider_value = range.value;
   let thumb_color;
   if (slider_value <= 29) {
       thumb_color = "rgb(255, 0, 0)";
   }
   else if (slider_value >= 30 && slider_value <= 69) {
       thumb_color = "rgb(255, 255, 0)";
   }
   else {
       thumb_color = "rgb(0, 255, 0)";
   }
   style.innerHTML = `.slider::-webkit-slider-thumb { background-color: ${thumb_color} !important; } .slider:-moz-range-thumb {  ${thumb_color} !important; }`;
});
.slider {
    width: 60%;
    margin: 50px auto;
    -webkit-appearance: none;
    height: 8px;
    border-radius: 4px;
    margin-bottom: 15px;
    background-color: rgb(200, 200, 200);
}

.slider::-webkit-slider-thumb {
   -webkit-appearance: none;
    width: 18px;
    height: 18px;
    border-radius: 10px;
    background-color: rgb(255, 120, 0);
    overflow: visible;
    cursor: pointer;
    transition: all 1s ease;
        
}

.slidecontainer {
    transform: translateY(-10px);
}
<style data="test" type="text/css"></style>

<div class="slidecontainer" align="center">
    <input id="rangeInput" type="range" min="0" max="100" value="50" class="slider" name="rangeInput">
</div>
RegarBoy
  • 3,228
  • 1
  • 23
  • 44