How to get color value from gradient by percentage with javascript?
Asked Answered
S

11

47

I have a fixed width div with gradient applied using css. I want to build slider based color picker based on this gradient.

When i drag the slider i calculate the percentage position, and i want to get the hex or rgb color code based on this value.

My idea was to create an array with the start/stop positions and colors defined, then find two values from this array based on the slider position, then somehow find the color between: this is where i can't move forward.

Demo: http://jsfiddle.net/pdu8rpfv/

var gradient = [
    [
        0,
        'ff0000'
    ],
    [
        28,
        '008000'
    ],
    [
        72,
        '0000ff'
    ],
    [
        100,
        'ff0000'
    ]
];
$( "#slider" ).slider({
    min: 1,
    slide: function( event, ui ) {

        var colorRange = []
        $.each(gradient, function( index, value ) {
            if(ui.value<=value[0]) {
                colorRange = [index-1,index]
                return false;
            }
        });

        $('#result').css("background-color", 'red');

    }
});
Spore answered 9/5, 2015 at 17:36 Comment(0)
S
92

I was able to solve this issue using this function, which is based on the less.js function: http://lesscss.org/functions/#color-operations-mix

function pickHex(color1, color2, weight) {
    var w1 = weight;
    var w2 = 1 - w1;
    var rgb = [Math.round(color1[0] * w1 + color2[0] * w2),
        Math.round(color1[1] * w1 + color2[1] * w2),
        Math.round(color1[2] * w1 + color2[2] * w2)];
    return rgb;
}

I just simply need to pass the two closest color codes from the gradient array and the ratio where the slider handle is located(which can be calculated easily with the help of the slider width). Here is the live example:

http://jsfiddle.net/vksn3yLL/

Spore answered 9/5, 2015 at 20:18 Comment(0)
K
15

There is a nice lib that handles variety of color manipulations chroma.js

yarn add chroma-js

And then

import chroma from 'chroma-js';

const f = chroma.scale(['yellow', 'red', 'black']);

console.log( f(0.25).toString() );                    // #ff8000
console.log( f(0.5).css('rgba') );                    // rgba(255,0,0,1)
console.log( f(0.75).css() );                         // rgb(128,0,0)
console.log( f(1).css() );                            // rgb(0,0,0)
Kurdish answered 10/7, 2020 at 20:5 Comment(0)
F
9

Three color gradient

Just in case someone wants a 3 color gradient, here's an example using red, yellow and green:

enter image description here

The github JS code for the colorGradient function is available here.

function colorGradient(fadeFraction, rgbColor1, rgbColor2, rgbColor3) {
    var color1 = rgbColor1;
    var color2 = rgbColor2;
    var fade = fadeFraction;

    // Do we have 3 colors for the gradient? Need to adjust the params.
    if (rgbColor3) {
      fade = fade * 2;

      // Find which interval to use and adjust the fade percentage
      if (fade >= 1) {
        fade -= 1;
        color1 = rgbColor2;
        color2 = rgbColor3;
      }
    }

    var diffRed = color2.red - color1.red;
    var diffGreen = color2.green - color1.green;
    var diffBlue = color2.blue - color1.blue;

    var gradient = {
      red: parseInt(Math.floor(color1.red + (diffRed * fade)), 10),
      green: parseInt(Math.floor(color1.green + (diffGreen * fade)), 10),
      blue: parseInt(Math.floor(color1.blue + (diffBlue * fade)), 10),
    };

    return 'rgb(' + gradient.red + ',' + gradient.green + ',' + gradient.blue + ')';
}

Demo:

// Gradient Function
function colorGradient(fadeFraction, rgbColor1, rgbColor2, rgbColor3) {
    console.log('>> fade: ', fadeFraction)
    var color1 = rgbColor1;
    var color2 = rgbColor2;
    var fade = fadeFraction;

    // Do we have 3 colors for the gradient? Need to adjust the params.
    if (rgbColor3) {
      fade = fade * 2;

      // Find which interval to use and adjust the fade percentage
      if (fade >= 1) {
        fade -= 1;
        color1 = rgbColor2;
        color2 = rgbColor3;
      }
    }

    var diffRed = color2.red - color1.red;
    var diffGreen = color2.green - color1.green;
    var diffBlue = color2.blue - color1.blue;

    var gradient = {
      red: parseInt(Math.floor(color1.red + (diffRed * fade)), 10),
      green: parseInt(Math.floor(color1.green + (diffGreen * fade)), 10),
      blue: parseInt(Math.floor(color1.blue + (diffBlue * fade)), 10),
    };
    return 'rgb(' + gradient.red + ',' + gradient.green + ',' + gradient.blue + ')';
}
  
// IMPLEMENTATION EXAMPLE:
var spans = $('.gradient-test');
var count = spans.length, color;
var color1 = {
  red: 19, green: 233, blue: 19
};
var color3 = {
  red: 255, green: 0, blue: 0
};
var color2 = {
  red: 255, green: 255, blue: 0
};
$('.gradient-test').each(function(i, span) {
  var g = Math.round(((i+1) * 100) / count);
  if (g < 10){
    g = '0' + g;
  } 
  g = +g === 100 ? 1 : +('0.' + g)
  color = colorGradient(g, color1, color2, color3);
  $(span).css('background-color', color);
});
.gradient-test {
  width: 1rem;
  height: 1rem;
  display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
<span class="gradient-test"></span>
Froe answered 23/4, 2020 at 20:47 Comment(0)
D
4

This is another way to convert percentage to color

This exemple make a displayed value change from red to green depending on it's value (like for example in excel conditional formating):

function(percentValue) {
  $(`#output`)
    // print the value
    .html(percentValue)
    // colorize the text, more red if it's close to 0
    // and more green as it approach 100
    .css({color: `rgb(${(100 - percent) *2.56}, ${percent *2.56},0)`})
}

Please see demo below:

$('button').click( e => {
  const percent = Math.floor(Math.random()*100);
  const newElm = $(`<b>${percent}</b><br>`)
  .css({color: `rgb(${(100 - percent) *2.56},${percent *2.56},0)`})
  $('body').append(newElm);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button>Click to make new percentage</button><br>
Dissuasive answered 5/1, 2019 at 22:19 Comment(0)
Z
4

Using similar logic as Felipe Ribeiro's top answer,

I have created a Javascript color-scales that handles it for you. You are also able to input multiple color stops as well as set a transparency level.

A live demo is here: https://codepen.io/dalisc/pen/yLVXoeR

Link to the package is here: https://www.npmjs.com/package/color-scales

Example usage:

const ColorScale = require("color-scales");
let colorScale = new ColorScale(0, 100, ["#ffffff", "#000000"], 0.5);
let rgbaStr = colorScale.getColor(50).toRGBAString(); // returns "rgba(127,127,127, 0.5)"

The package is able to output a hex, rgb, or rgba string depending on your needs. It can also output a custom Color object ({r,g,b,a}) in case you need to cherry-pick the individual color components.

I came across a similar issue when trying to mimick a Tableau dashboard on a Javascript-based application. I realised it is a common feature of data visualization tools such as Tableau and Microsoft Excel, so I have created an npm package to handle it on Javascript application. If you would like to reduce your code, you could use the package.

Ziwot answered 18/2, 2021 at 16:31 Comment(0)
B
3

Based on Gils answer above I've made a class that works with gradients with colors set at specific percentages along linear gradient. So it works easily with more complex gradients copypasted from CSS.

// background: linear-gradient(90deg, #386657 0%, #85B87A 24.48%, #FFE600 51.56%, #BA4F1A 80.21%, #940023 100%);
const grad = new LinearGradientHelper([
  ['#386657', 0],
  ['#85B87A', .2448],
  ['#FFE600', .5156],
  ['#BA4F1A', .8021],
  ['#940023', 1]
])

console.log(grad.getColor(0))
console.log(grad.getColor(.1))
console.log(grad.getColor(.4))
console.log(grad.getColor(.95))
console.log(grad.getColor(1))

https://jsfiddle.net/ctk916xa/2/

Babbette answered 20/11, 2021 at 21:11 Comment(2)
Actually, the getColor function gets percent as input, so we should use grad.getColor(95) instead of grad.getColor(.95).Scriabin
I took this sample and converted it to TypeScript to remove some of the browser console errors:Lesbianism
D
3

This is my realization for multiple color gradient set RGB. (Without alpha channel). Extended variant of previous answer.

r1.addEventListener('change',(ev)=>{
  let res = test(ev.target.value)
  d2.innerText=ev.target.value +' color: '+res
  d2.style.backgroundColor = res
})
function test(value){
  let colorPicked = pickRgbRange(value,
       {color:[255,0,228,1], position:0},
       {color:[0,194,255,1,1], position:15},
       {color:[35,200,0,1], position:35},
       {color:[255, 250, 164], position:50},
       {color:[255,0,0,1], position:75},
       {color:[0,0,0,1], position:100}
       );
  let resultRgba = `rgba(${colorPicked[0]},${colorPicked[1]},${colorPicked[2]},${colorPicked[3]})`;
     return resultRgba
}

//(ildarin cc0) Start copy from here: ----------------------------------
/** @description usage
   let colorPickedRgba = pickRgbRange(value,
     {color:[255,0,228,1], position:0},
     {color:[0,194,255,1,0.5], position:.15},
     {color:[35,200,0,1], position:.35},
     {color:[255, 250, 164], position:.50},
     {color:[255,0,0,1], position:.75},
     {color:[0,0,0,1], position:.100}
     )
     let resultRgba = `rgba(${colorPicked[0]},${colorPicked[1]},${colorPicked[2]},${colorPicked[3]})`
*/
function pickRgbRange(position, ...elements) {
    var [left, right, weight] = pickClosest(position, ...elements);
    return pickRgba(left.color, right.color, weight);
}

function pickRgba(color1, color2, weight) {
    var w1 = weight;
    var w2 = 1 - w1;
    var rgba = [
        Math.round(color1[0] * w2 + color2[0] * w1),
        Math.round(color1[1] * w2 + color2[1] * w1),
        Math.round(color1[2] * w2 + color2[2] * w1),
        1
    ];
    return rgba;
}

function pickClosest(position, ...elements) {
    var left = elements[0], 
    right = { color: [0, 0, 0], position: Number.MAX_VALUE };
    var leftIndex = 0;
    for (var i = 0; i < elements.length; i++) {
        if (position >= elements[i].position && position > left.position){
            left = elements[i];
            leftIndex = i;
        }
    }
    if (elements.length - 1 === leftIndex) {
        right = elements[leftIndex];
    }
    else {
        right = elements[leftIndex + 1];
    }
    if(left == right){
      return [right, right, 0];
    }
    var dleft = position - left.position;
    var sum = dleft + right.position - position;
    var weight = dleft / sum;
    return [left, right, weight];
}
#r1{
width:100%;
}
#d1,
#d2 {
  width: 100%;
  height: 50px;
}

#d1 {
  background: linear-gradient(90deg,
      rgb(255, 0, 228) 0%,
      rgb(0, 194, 255) 15%,
      rgb(35, 200, 0) 35%,
      rgb(255, 250, 164) 50%,
      rgb(255, 0, 0) 75%,
      rgb(0, 0, 0) 100%);
}

#d2 {
  text-shadow:0 0 4px #fff;
  background-color: #ccc;
}
<div id='d1'></div>
<input id='r1' type='range' />
<div id='d2'></div>
Duvall answered 26/12, 2021 at 20:40 Comment(1)
Man, that's gold! Thank you!Dumps
O
2

Infinite number of colors gradient (two or more)

If your have 2, 3, 4 or twenty colors you can use this solution. Generate an HTML element and style it with CSS gradient background. Then look at a single pixel color value.

  1. Create a <canvas> element. with width of 101px (0 to 100 percent) and height 1px (we don't care about height). Then set the background-color to gradient. See method initCanvas(colorsArray).

  2. Look at a single pixel of the canvas, and return it's color. See method getColor(percent).

  3. At the end you can find and example of gradient made from 5 colors (red, orange, green, lime, blue).

const WIDTH = 101; // 0 to 100
const HEIGHT = 1;
  
let context;

function initCanvas(gradientColors) // gradientColors [colorA, colorB, ..]
{
  // Canvas
  const canvasElement = document.createElement("CANVAS");
  canvasElement.width = WIDTH;
  canvasElement.height = HEIGHT;

  context = canvasElement.getContext("2d");
  
  // Gradient
  const gradient = context.createLinearGradient(0, 0, WIDTH, 0); // x0, y0, x1, y1
  
  const step = 1 / (gradientColors.length - 1); // need to validate at least two colors in gradientColors
  let val = 0;
  gradientColors.forEach(color => {
    gradient.addColorStop(val, color);
    val += step;
  });

  // Fill with gradient
  context.fillStyle = gradient;
  context.fillRect(0, 0, WIDTH, HEIGHT); // x, y, width, height
}

function getColor(percent) // percent [0..100]
{
  const color = context.getImageData(percent, 0, 1, 1); // x, y, width, height
  const rgba = color.data;
  
  return `rgb(${ rgba[0] }, ${ rgba[1] }, ${ rgba[2] })`;
}

// Test:
initCanvas(['red', 'orange', 'green', 'lime', 'blue']);

document.getElementById("color0"  ).style.backgroundColor = getColor(0);
document.getElementById("color10" ).style.backgroundColor = getColor(10);
document.getElementById("color20" ).style.backgroundColor = getColor(20);
document.getElementById("color30" ).style.backgroundColor = getColor(30);
document.getElementById("color40" ).style.backgroundColor = getColor(40);
document.getElementById("color50" ).style.backgroundColor = getColor(50);
document.getElementById("color60" ).style.backgroundColor = getColor(60);
document.getElementById("color70" ).style.backgroundColor = getColor(70);
document.getElementById("color80" ).style.backgroundColor = getColor(80);
document.getElementById("color90" ).style.backgroundColor = getColor(90);
document.getElementById("color100").style.backgroundColor = getColor(100);
.example {
  width: 100px;
  height: 25px;
}
<h3>Gradient colors: red, orange, green, lime, blue</h3>
<div id="color0"   class="example">0%  </div>
<div id="color10"  class="example">10% </div>
<div id="color20"  class="example">20% </div>
<div id="color30"  class="example">30% </div>
<div id="color40"  class="example">40% </div>
<div id="color50"  class="example">50% </div>
<div id="color60"  class="example">60% </div>
<div id="color70"  class="example">70% </div>
<div id="color80"  class="example">80% </div>
<div id="color90"  class="example">90% </div>
<div id="color100" class="example">100%</div>

P.S - I code with two methods initCanvs() and getColors() so you won't generate new canvas for each color selection. But you can merge them if you have new gradient each time.

Onofredo answered 3/8, 2020 at 17:35 Comment(1)
Great solution to a sticky problem. As a note, I had more than 10 colours in my array and had to remove the minus one from const step = 1 / (gradientColors.length - 1); to prevent an error when val goes above 1 (due to rounding). Thanks Gil.Scaife
F
0

My two cents, calculating a linear for a list of arbitrary vector, of an arbitrary dimension, with arbitrary stops (might be useful for CYMK gradients, but really I wrote this because I don't like repeating code for red, green, and blue):

function binarySearch(arr, el, cmp) {
    // thanks to https://stackoverflow.com/questions/22697936
    let m = 0;
    const n = arr.length - 1;
    while (m <= n) {
        let k = (n + m) >> 1;
        let z = cmp(el, arr[k]);
        if (z > 0) {
            m = k + 1;
        } else if (z < 0) {
            n = k - 1;
        } else {
            return k;
        }
    }
    return ~m;
}
function LinearGradient(colors, stops) {
    if (colors.length !== stops.length) {
        throw new Error('Number of stops must equad to number of colors')
    }
    if (colors.length < 2) {
        throw new Error('Must specify at least two colors')
    }
    if (stops[0] !== 0) {
        // add explicit zero stop if not specified
        stops = [0, ...stops];
        colors = [colors[0], ...colors];
    }
    if (stops[1] !== 1) {
        // add explicit final stop if not specified
        stops = [...stops, 1];
        colors = [...colors, colors.slice(-1)[0]];
    }
    if (stops.some(stop => stop < 0 || stop > 1)) {
        throw new Error('All stops must be in the range 0..1')
    }
    if (stops.some((stop, i) => i > 0 && stop < stops[i-1])) {
        throw new Error('Stops must be sorted')
    }
    return (pos) => {
        let i = binarySearch(stops, pos, (a, b) => a - b);
        if (i < 0) i = ~i;
        const i1 = i == 0? 0 : i-1;
        const i2 = i == 0? 1 : i;
        const s1 = stops[i1], s2 = stops[i2];
        const c1 = colors[i1], c2 = colors[i2];
        const rel = ((pos - s1) / (s2 - s1));
        const pt = c1.map((c, i) => Math.round(c * (1-rel) + c2[i] * rel));
        return pt;
    }
}
const rgb = (c) => `rgb(${c[0]}, ${c[1]}, ${c[2]})`;

And then some testing code:

[0,0.01,0.25,0.5,0.75,0.99,1].map(LinearGradient([[255,0,0],[255,255,0],[0,255,0]],[0,0.5,1])).map(rgb)
// ['rgb(255, 0, 0)', 'rgb(255, 5, 0)', 'rgb(255, 128, 0)', 'rgb(255, 255, 0)', 'rgb(128, 255, 0)', 'rgb(5, 255, 0)', 'rgb(0, 255, 0)']

This doesn't include a lot of comments, but in short LinearGradient() accepts a list of colors (as vectors, or 3-element arrays) and stops (where each color is at it's peak) and returns you a function that evaluates the gradient at different points within the range 0..1. The utility function rgb is used to generate an rgb() color expression for use with CSS.

Fescennine answered 2/3, 2023 at 0:4 Comment(0)
L
0

Converted Tarwin's answer to a TypeScript class which removes Browser console warning and typescript warnings

export class LinearGradientHelper 
{
    
    readonly WIDTH:number = 101 // 0 to 100
    readonly HEIGHT:number = 1
    context = null
  
    constructor(gradientColors) 
    { // [ [color, % ie 0, 0.5, 1], [ ... ], ... ]
    
        // Canvas
    const canvasElement:HTMLCanvasElement = document.createElement("CANVAS") as HTMLCanvasElement;
    canvasElement.width = this.WIDTH;
    canvasElement.height = this.HEIGHT;
  
    this.context = canvasElement.getContext('2d', { willReadFrequently: true });
    const gradient = this.context.createLinearGradient(0, 0, this.WIDTH, 0); // x0, y0, x1, y1
    
    gradientColors.forEach(val => {
      gradient.addColorStop(val[1], val[0]);
    });
  
    // Fill with gradient
    this.context.fillStyle = gradient;
    this.context.fillRect(0, 0, this.WIDTH, this.HEIGHT); // x, y, width, height
  }
  
  GetColor(percent) // percent [0..100]
  {
    const color = this.context.getImageData(parseInt(percent), 0, 1, 1); // x, y, width, height
    const rgba = color.data;
    
    return `rgb(${ rgba[0] }, ${ rgba[1] }, ${ rgba[2] })`;
  }
}
Lesbianism answered 28/2 at 12:44 Comment(0)
D
0

Adding to this long list of good answers, I have a pure CSS solution using color-mix(). You can recursively nest color-mix() and use a custom property to get the color at a point along a gradient.

So if you have this gradient

.gradient {
    background-image: linear-gradient(to right, red, blue 30%, coral 80%);
}

You can get its value at --t (from 0 to 1) with this:

.mix {
    --t: 0;
    background-color: color-mix(
        in srgb,
        red,
        color-mix(
            in srgb,
            blue,
            coral
            clamp(0%, (var(--t) / (.8 - .3) - .3 / (.8 - .3)) * 100%, 100%)
        )
        clamp(0%, var(--t) / .3 * 100%, 100%)
    );
}

I describe this in much greater detail in a blog post, including linking to a CodePen with a Sass function to generate the CSS.

Dozier answered 2/5 at 17:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.