Home > Software engineering >  How to create a color picker circle like Phillips Hue
How to create a color picker circle like Phillips Hue

Time:11-15

I'm trying to create a color picker in javascript canvas. I want to make something like this:

color picker

I want that when i click anywhere in the circle, i get x, y coordinates (so I can use some other element to mark the selected color) and color. I would prefer if the solution was not a canvas with embedded image, but something like linear-gradient.

I have searched the internet, but have not found anything to my question.

P.S.: Maybe css border-radius will help to make a circle.

CodePudding user response:

If you really don't want to use an image, you need to currently draw each pixel by hand. I'm sure there's mathematical functions for this somewhere.

The reason you can't do this using gradients is because to get a gradient color wheel like that, you would need to stack a radial gradient and a conic gradient. The conic gradient will create the color wheel, the radial gradient will provide the white overlay that spreads out from the center.

You cannot currently do this using a canvas, because not all browsers currently support the createConicGradient function on a canvas. In those browsers, you can only use radial gradients (and linear gradients, of course, but those won't help for this).

You can use stacked CSS background gradients, but browsers do not currently have a reliable way to determine an individual pixel's background color at specific X/Y coordinates, except within a canvas. And since CSS background styles aren't part of a canvas's context, that won't work. There are workarounds that make use of the html2canvas library, but this has the same problem in browsers that do not support conic gradients on canvases.

Browsers are starting to implement the EyeDropper API but this is still rare to find and even if browsers do have it, it's an unstable API and not really suited for production use. But if it's supported you could use that with the stacked CSS background gradients on a regular div like so, no canvas needed:

document.addEventListener('DOMContentLoaded', function() {
  document.getElementById('color-wheel').addEventListener('click', function() {
    (new EyeDropper()).open().then(function(result) {
      document.getElementById('color').innerText = result.sRGBHex;
    });
  });
});
#color-wheel {
  width: 150px;
  height: 150px;
  background: radial-gradient(white, transparent 80%),
              conic-gradient(#e43f00, #fae410, #55cc3b, #09adff, #6b0efd, #e70d86, #e43f00);
  border-radius: 50%;
}
<div id="color-wheel"></div>
Color: <span id="color"></span>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

But as I said, this is unstable and will definitely not work in all browsers for the foreseeable future.

CodePudding user response:

Disclaimer: this answer is intended as an improovement of rickdenhaan's answer.

You can stack css gradients to obtain the color wheel, and use simple math to compute the color at given coordinates without actually picking the color from the wheel. This approach doesn't use EyeDropper, so it should have full browsers support. In the following code I have bind the code to the mousemove event to test it easily; just replace the event name with "click" to get the expected behaviour:

const colors = [
    {r: 0xe4, g: 0x3f, b: 0x00},
    {r: 0xfa, g: 0xe4, b: 0x10},
    {r: 0x55, g: 0xcc, b: 0x3b},
    {r: 0x09, g: 0xad, b: 0xff},
    {r: 0x6b, g: 0x0e, b: 0xfd},
    {r: 0xe7, g: 0x0d, b: 0x86},
    {r: 0xe4, g: 0x3f, b: 0x00}
];
document.addEventListener('DOMContentLoaded', function() {
    document.getElementById('color-wheel').addEventListener('mousemove', function(e) {
        var rect = e.target.getBoundingClientRect();
        //Compute cartesian coordinates as if the circle radius was 1
        var x = 2 * (e.clientX - rect.left) / (rect.right - rect.left) - 1;
        var y = 1 - 2 * (e.clientY - rect.top) / (rect.bottom - rect.top);
        //Compute the angle in degrees with 0 at the top and turning clockwise as expected by css conic gradient
        var a = ((Math.PI / 2 - Math.atan2(y, x)) / Math.PI * 180);
        if (a < 0) a  = 360;
        //Map the angle between 0 and number of colors in the gradient minus one
        a = a / 360 * (colors.length - 1);  //minus one because the last item is at 360° which is the same as 0°
        //Compute the colors to interpolate
        var a0 = Math.floor(a) % colors.length;
        var a1 = (a0   1) % colors.length;
        var c0 = colors[a0];
        var c1 = colors[a1];
        //Compute the weights and interpolate colors
        var a1w = a - Math.floor(a);
        var a0w = 1 - a1w;
        var color = {
            r: c0.r * a0w   c1.r * a1w,
            g: c0.g * a0w   c1.g * a1w,
            b: c0.b * a0w   c1.b * a1w
        };
        //Compute the radius
        var r = Math.sqrt(x * x   y * y);
        if (r > 1) r = 1;
        //Compute the white weight, interpolate, and round to integer
        var cw = r < 0.8 ? (r / 0.8) : 1;
        var ww = 1 - cw;
        color.r = Math.round(color.r * cw   255 * ww);
        color.g = Math.round(color.g * cw   255 * ww);
        color.b = Math.round(color.b * cw   255 * ww);
        //Compute the hex color code and apply it
        var xColor = rgbToHex(color.r, color.g, color.b);
        document.getElementById('color').innerText = xColor;
        document.getElementById('color').style.backgroundColor = xColor;
    });
});

function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0"   hex : hex;
}

function rgbToHex(r, g, b) {
    return "#"   componentToHex(r)   componentToHex(g)   componentToHex(b);
}
#color-wheel {
    width: 150px;
    height: 150px;
    background: radial-gradient(white, transparent 80%),
                conic-gradient(#e43f00, #fae410, #55cc3b, #09adff, #6b0efd, #e70d86, #e43f00);
    border-radius: 50%;
}
<div id="color-wheel"></div>
Color: <span id="color"></span>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related