Red, Amber and Green progress bar
Tags: css, javascript
Published: 2020-07-01 05:24:07

---
title: Red, Amber and Green progress bar
published: true
date: 2020-06-23 05:38:00 UTC
tags: CSS,JavaScript
canonical_url: https://drmsite.blogspot.com/2020/06/red-amber-and-green-progress-bar.html
---

[![](https://1.bp.blogspot.com/-2-tIJELjwkA/XvGSZEQp3lI/AAAAAAAAnRk/McH5xJL1-EYOEKbpIPxzJNu4Zd4NxcuqwCLcBGAsYHQ/s320/red-amber-green.gif)](https://1.bp.blogspot.com/-2-tIJELjwkA/XvGSZEQp3lI/AAAAAAAAnRk/McH5xJL1-EYOEKbpIPxzJNu4Zd4NxcuqwCLcBGAsYHQ/s1600/red-amber-green.gif)

I've been doing some Moodle theming over the past few weeks and ended up discussing progress bars with the [designer](https://the-dots.com/users/cyrill-vepritzky-151198) 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](https://drmsite.blogspot.com/2010/06/place-for-amber.html). 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 <abbr title="Red Green and Blue">RGB</abbr> 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 `trim`ming.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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](https://jsfiddle.net/annoyingmouse/L9ouxngk/).