Blend colors between values

1.1k Views Asked by At

I am trying to create like a time countdown using canvas arc, the drawing is ok, but the arc will have a stroke color, which I want to sort of blend.

The way I draw the arc is by using a number between 0 to 100, 100 is basically the full arc, 0 is nothing. When the value is over 66 and under (or equal to) 100 it'll be a blend between green and orange, 100 is total green and 66 is total orange. 65 and down will be a mix between orange and red, where total red is 0.

I am clueless on where to start, no ideas on what to search for to do research on how to get the values.

Can anybody please point me in the right direction?

Here's my code so far x)

static color (value)
{
    const StartColor  = 0x66BB6A; // Green
    const MiddleColor = 0xFFA726; // Orange
    const EndColor    = 0xEF5350; // Red  
}

EDIT: This is the result I was looking for. It's now working exactly like I want it to. Cheers.

https://youtu.be/IeR_zMzSaAU

4

There are 4 best solutions below

1
Mark On BEST ANSWER

Of course, using a library dedicated to this will be easier. But just in case anyone is interested in doing the math, you can break the hex integers into components and then interpolate each component separately to get the color at a specific place:

// break hex integer into components:
const getComonents = (color) => Array.from({length: 3}, (_, i) => Math.floor(color / 16 ** (2 * i) % 16 ** 2))

// interpolate arrays by component
const interpolate = (arr1, arr2, percent) => arr1.map((v, index) => Math.floor(v + (arr2[index] - v) * (percent / 100)))

function colors(value) {
  const StartColor  = 0x11FF11; // Green
  const MiddleColor = 0xFFA726; // Orange
  const EndColor    = 0xEF5350; // Red
  
  let [start, end, v] = value < 50 
      ? [StartColor, MiddleColor, value * 2 ] 
      : [MiddleColor, EndColor, (value - 50) * 2]

  let interpoled = interpolate(getComonents(start), getComonents(end), v)
  
  return interpoled.reduce((n, component, index) => n + component * (16 ** (index * 2)), 0)
}

const canvas = document.getElementById('the_canvas')
const ctx = canvas.getContext('2d')

for (let i = 0; i<100; i++){
  ctx.beginPath();
  
  // padStart is needed to make sure a value of `ff` becomes `0000ff`
  ctx.fillStyle = "#" + colors(i).toString(16).padStart(6, '0')
  ctx.rect(i*5, 0, i*5+5, 300);
  ctx.fill()
}
<canvas id="the_canvas" width="500" height="300"></canvas>

5
a--m On

Note: This answer is a late and big edit from the original one. After a first attempt were I missunderstood the OP intentions, the OP added a link with an example, since then I had the intention to provide a solution since I wasn't satisfied with the current answers and the exercice was fun to build.


The breakdown of the answer goes into two main parts:

  1. Blend the colors (there are already answers that tackle this, using a library or create a method to interpolate/blend the color hex value)
  2. Draw an arc and use the blended color

/*
 * blending colors 
 * (borrowed and modified from @mark-meyer answer)
 */

// break hex integer into components:
const getComponents = (color) => Array.from({length: 3}, (_, i) => Math.floor(color / 16 ** (2 * i) % 16 ** 2))

// interpolate arrays by component
const interpolate = (arr1, arr2, percent) => arr1.map((v, index) => Math.floor(v + (arr2[index] - v) * (percent / 100)))

const colorBlend = value => {
  const StartColor  = 0x11FF11; // Green
  const MiddleColor = 0xFFA726; // Orange
  const EndColor    = 0xEF5350; // Red
  
  let [start, end, v] = value < 50 
      ? [StartColor, MiddleColor, value * 2 ] 
      : [MiddleColor, EndColor, (value - 50) * 2]

  let interpoled = interpolate(getComponents(start), getComponents(end), v)
  
  const color = interpoled
  .reduce((n, component, index) => n + component * (16 ** (index * 2)), 0)
  .toString(16).padStart(6, '0')
  return `#${color}`
}

/*
 * draw an arc
 */
const canvas = document.getElementById('canvas1')
const ctx = canvas.getContext('2d')
const size = 30
let p = 0

const grad = ctx.createLinearGradient(size, size, size * 2, size * 2)
grad.addColorStop(0, "#66BB6A")
grad.addColorStop(0.5, "#FFA726")
grad.addColorStop(1, "#EF5350")

ctx.lineWidth = 6

const draw = () => {
  p += 0.01
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  ctx.strokeStyle = colorBlend(p * 50)
  ctx.beginPath()
  ctx.arc(size * 1.5, size * 1.5, size, 1.5 * Math.PI, (1.5 + p) * Math.PI)
  ctx.stroke()
  if (p < 2) {
    window.requestAnimationFrame(draw);
  }
}

window.requestAnimationFrame(draw);
<canvas id="canvas1" width="500" height="500"></canvas>

1
ksav On

I have assumed that you want to

  • create a color scale based on three known colors - '66BB6A', 'FFA726', 'EF5350'
  • input a number from within a domain (0 - 100) and get a new color calculated from the scale.

Chroma.js is a nice little js library that can make color scales and conversions.

const colorScale = chroma.scale(['66BB6A', 'FFA726', 'EF5350']).domain([0,100]);

console.log(colorScale(20).toString())

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = colorScale(20);
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
ctx.fill();
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.0.2/chroma.min.js"></script>
<canvas id="myCanvas"></canvas>

2
Peter Bode On

Here is a quick fiddle I made.

var c = document.getElementById("foo");
var i = 0;
var timer = setInterval(function() {

    var ctx = c.getContext("2d");
    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, (i / 100) * Math.PI);
    ctx.strokeStyle = 'RGB('+((100-i)*2)+', '+i+', 0)';
    ctx.stroke();

    if((i / 100) == 2) {
        clearInterval(timer);
        console.log('FULL');
    }
    i++;
}, 100);