Red, Amber and Green progress bar

Dominic Myers - Jul 1 '20 - - Dev Community

I've been doing some Moodle theming over the past few weeks and ended up discussing progress bars with the designer involved. Little progress is bad whereas finishing a Moodle course is good so I, of course, started waxing lyrical about colours. If red means bad and green means good , what about the middle ground? Then I remembered a traffic light visualisation I was working on ages ago and the joys of amber. Interestingly that post is ten years old! I must've been thinking about colours all these years - guess that's a good ( green?) thing for a front-end developer!

Anyway, the project I'm working on, as I said, it based within Moodle and writing JS for that is something of a challenge. I can't get my head around the module system they use, and I don't want to learn it enough to get access to jQuery within a theme's script, so I wrote it in Vanilla JS.

I had CSS variables for the colours, so first I clocked I needed those within the script. Still, I also needed them in terms of the RGB values - as an array would work fine, after some research I came up with hexToRGB, which produces a simple collection with the RGB values as three distinct integer values in an array. One possible issue with using JS to get the computed style from a CSS variable is that it returns everything from the colon to the semi-colon as a string. That's good and all, but that left an extraneous space which needed trimming.

const hexToRGB = hex => hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#' + r + r + g + g + b + b).substring(1).match(/.{2}/g).map(x => parseInt(x, 16))
const red = hexToRGB(getComputedStyle(document.documentElement).getPropertyValue('--under-third').trim())
const amber = hexToRGB(getComputedStyle(document.documentElement).getPropertyValue('--third-to-two-thirds').trim())
const green = hexToRGB(getComputedStyle(document.documentElement).getPropertyValue('--over-two-thirds').trim())

I also needed to mix the colours, which wasn't much of a bother. Though I did discover there were multiple ways of mixing colours, I chose to go the most straightforward route with mixColour (hey, what can I say? I know I'm supposed to use color when I code HTML and CSS, but I much prefer colour). mixColour uses a further function, imaginatively called mix, where the heavy lifting occurs and takes into account the percentage of each colour needing mixing, we pass this as a float between 0 and 1.

const mixColour = (c1, c2, pc) => RGBToHex(Math.round(mix(c1[0], c2[0], pc)), Math.round(mix(c1[1], c2[1], pc)), Math.round(mix(c1[2], c2[2], pc)))
const mix = (s, e, pc) => s + ((pc) \* (e - s))

Once mixed, I needed to produce RGB values again, so I worked up RGBToHex which uses some smart bit-wise operators - I wish I knew what they did. Still, I use bit-wise operators so infrequently that I can't seem to work up the enthusiasm to learn them properly.

const RGBToHex = (r, g, b) => `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`

We had values between 0 and 100 for the progress bar, so working out the float value was the only thing that caused me any head-scratching. If the amount was less than 50, then red and amber needed to be mixed by the value divided by 50; if the value was over 50 then we use amber and green: we need to subtract 50 from the value and then, again, divide the result by 50.

let value = 0
const container = document.getElementById('bar')
const bar = container.querySelector('.progress-bar-filler')
const interval = setInterval(
  () => {
    value = value === 100 ? 0 : value + 1
    container.setAttribute('data-value', value)
    bar.style.width = value + "%"
    bar.style.backgroundColor = value < 50
      ? mixColour(red, amber, (value / 50))
      : mixColour(amber, green, ((value - 50) / 50))
  }, 100)

Simples eh? Once it's all broken down, then it's just a collection of function, and it works a treat too. There are again some dodgy colours produced between amber and green - but that's only for a very little time, something like 5% I think.

All that was left was to work up a demo.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player