Search code examples
javascriptreactjsanimationcolors

Interpolate between two colours based on a percentage value


I'm currently developing an animation library in JS and need to calculate a colour between two values based on a progress value between 0 - 1.

For example, the function might look something below. This would input two colours in HEX format.

  • colourA = Inital Colour
  • colourB = Final Colour
  • progress = 0.5 (50%)

const interpolateColour = (colourA, colourB, progress) => { return polColour } 

The goal is to return a colour 0.5 progress or 50% between the two colours. I know HEX colour would most likely need to be converted to RGBA for HSV to accomplish this effect, although unsure which is the best approach.

EDIT: I figured it out...


const is = {
    hex: a => /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a),
    rgb: a => /^rgb/.test(a),
    hsl: a => /^hsl/.test(a),
    col: a => (is.hex(a) || is.rgb(a) || is.hsl(a)),
}

const convertToRgba = (colour) => {
    return is.hex(colour) ? hexToRgba(colour)
        : is.rgb(colour) ? rbgToRgba(colour)
            : is.hsl(colour) ? hslToRgba(colour)
                : colour
}

const hexToRgba = (colour, alpha = 1) => {
    const [r, g, b] = colour.match(/\w\w/g).map(x => parseInt(x, 16))
    return `rgba(${r},${g},${b},${alpha})`
};

const rbgToRgba = (colour, alpha = 1) => {
    const [r, g, b] = colour.replace(/[^\d,]/g, '').split(',')
    return `rgba(${r},${g},${b},${alpha})`
}

const deconstructRgba = (rgba) => {
    return rgba.replace(/[^\d,]/g, '').split(',').map(x => parseInt(x))
}

const formatRbga = (colour) => {
    return `rgba(${colour.r},${colour.g},${colour.b},${colour.a})`
}

const interpolateColour = (colourA, colourB, progress) => {
    const [r1, g1, b1, a1] = deconstructRgba(convertToRgba(colourA))
    const [r2, g2, b2, a2] = deconstructRgba(convertToRgba(colourB))
    return formatRbga({
        r: Math.round((r1 + r2) * progress),
        g: Math.round((g1 + g2) * progress),
        b: Math.round((b1 + b2) * progress),
        a: Math.round((a1 + a2) * progress)
    })
}

export {
    interpolateColour,
    convertToRgba,
    hexToRgba,
    rbgToRgba,
    deconstructRgba
}

Solution

  • A simple interpolation of the R, G, and B values should suffice.

    // extract numeric r, g, b values from `rgb(nn, nn, nn)` string
    function getRgb(color) {
      let [r, g, b] = color.replace('rgb(', '')
        .replace(')', '')
        .split(',')
        .map(str => Number(str));;
      return {
        r,
        g,
        b
      }
    }
    
    function colorInterpolate(colorA, colorB, intval) {
      const rgbA = getRgb(colorA),
        rgbB = getRgb(colorB);
      const colorVal = (prop) =>
        Math.round(rgbA[prop] * (1 - intval) + rgbB[prop] * intval);
      return {
        r: colorVal('r'),
        g: colorVal('g'),
        b: colorVal('b'),
      }
    }
    
    
    function doit(progression) {
      const div1 = document.getElementById('color1');
      const color1 = div1.style.backgroundColor;
      const div2 = document.getElementById('color2');
      const color2 = div2.style.backgroundColor;
    
      const rgbNew = colorInterpolate(
        color1,
        color2, progression
      );
    
      const divResult = document.getElementById('result');
      divResult.style.backgroundColor =
        `rgb( ${rgbNew.r}, ${rgbNew.g}, ${rgbNew.b})`;
    }
    
    document.querySelector('button').onclick = () => {
      const intval = Number(document.querySelector('input').value);
      doit(intval);
    };
    #color1,
    #color2,
    #result {
      width: 200px;
      height: 40px;
      margin: 12px;
      padding: 0.2rem 0.5rem;
    }
    <h4>Interpolate Between Two Colors</h4>
    <label for="number">Interpolate by:</label>
    <input id="number" type="number" value="0.5" min="0" max="1" step="0.05" />
    <button>Interpolate</button>
    
    <div style="display: flex">
      <div id="color1" style="background-color: #922B21">color1</div>
      <div id="result" style="background-color: #e0e0e0">interpolated</div>
      <div id="color2" style="background-color: #85C1E9">color2</div>
    </div>