How to Create a Color Picker Web App using JavaScript - Part 2

Nitish Kumar Singh

Feb 12, 2024

Hello developers! In this blog post, we will continue the color-picker web app project and understand how to write JavaScript logic code for our color-picker.

Creating Color-Picker Web App Part 2

In the previous part, we learned and understood how to design different UI parts of the color picker using HTML and CSS. If you have not read this blog post, then please do so first to understand the design part of the Color-Picker.

As we can see in the image above, the color-picker is made using many linear gradients and it works on the principle of linear gradient. In the main (big) color palette, there are many linear gradients from left to right. At the top-most edge, there is a gradient from white to a color (let's say color A), and as we move down, there are many gradient lines from left to right with different start and end colors, and the bottom edge is completely black from left to right.

So, when we are writing the logic of the color-picker, there are two following calculations that need to be done:

Find Color Between Two Gradient Ends: When the user drags the thumb on the main color palette, we have the position or coordinates of the thumb in the main palette, colors at all four vertices of the main palette rectangle. Then, we need to calculate the color at that position using the values that we have. Similarly, when the user drags the thumb on the colors-slider and alpha-slider, we need to calculate the color between two gradient ends.

Find Position Between Two Gradient Ends: When the user passes the default or selected color on opening (showing) of the color-picker and when changing Red, Blue, Green, Alpha, and Hex code from the given input elements, then we need to calculate the position of the thumb in the respective palette or sliders.

Now you may be wondering how to do these calculations, like I did when working on this project. Maybe you remember and can connect this question with the Intersection Formula that we all studied in 10th and 12th standards.

Where we have two given points and another point of unknown coordinates that intersects the line joining these points internally or externally with a given ratio, and we did calculate the coordinate of this other point or calculate the ratio when the coordinate is given using the "Intersection Formula".

Below is that Intersection Formula for calculating the coordinate of both the internal or external point:

When I ask ChatGPT for the calculation of the color between two color gradient ends (color stops, when the gradient has more than two colors), then ChatGPT gives the following formula:

ColorComponent = (1 - t) * ColorComponent_ofColorA + t * ColorComponent_ofColorB

Where t is a percentage in the range from 0 to 1, and ColorComponent_ofColorA and ColorComponent_ofColorB are the respective RGB values of Color A and Color B for the specific component (R, G, or B).

I have verified that this formula also came from the intersection formula in a short form. But, there is a slight difference here, and that is the use of percentage instead of ratio. For example, when point C divides line AB in the ratio AC/CB in the intersection formula, then t = AC/AB in the ChatGPT given formula.

Because calculated color components using the above formula may be a decimal number, so round it to ensure they fall within the valid range (0 to 255 for each RGB component).

Therefore, below is the final JavaScript function that returns the color components array of a color that divides the line of gradient of Color A and Color B in percentage t.

const getColorBetween = (colorA, colorB, t)=> {
    let r = Math.round((1 - t) * colorA[0] + t * colorB[0]);
    let g = Math.round((1 - t) * colorA[1] + t * colorB[1];
    let b = Math.round((1 - t) * colorA[2] + t * colorB[2];
    return [ r, g, b ];
}

So, now maybe you know how to calculate the position (percentage). When the color is given, assume the ratio is t and find one color component and then equate this calculated value with the given color component and find the value of t.

Till now, we have talked about logical concepts used in writing the logic of our color-picker. If you read the first part blog post, then you know that the main palette shows only a single color at the top-right corner with darkness increasing from top to bottom and brightness increasing right to left from 0 to 100%. And, this color changes by moving the thumb of the color-slider. When the thumb of the main palette is moved, then this is the final color selected by the user.

To calculate the color in the color-slider based on the position of the thumb, we use the following JS function (you can see the 'this' keyword used in the below code or codes provided in this post because all codes have been written in a class):

 onSliderDrag = (thumbPosition) => {
    let color;
    for (let i = 0; i < colorStops.length - 1; i++) {
      if (thumbPosition >= colorStops[i].position && thumbPosition <= colorStops[i + 1].position) {
        const percentage = (thumbPosition - colorStops[i].position) / (colorStops[i + 1].position - colorStops[i].position);
        color = getColorBetween (colorStops[i].color, colorStops[i + 1].color, percentage);
        break;
      }
    }
    // here will have also more code to calculate final selected color
};

Where colorStops is an array of objects with exactly the same color and position that is used in creating a linear-gradient to set on the color-slider as background and thumbPosition is the position of the thumb in the slider in percentage from 0 to 100.

const colorStops = [
  { color: [255, 0, 0], position: 0 },
  { color: [255, 255, 0], position: 17 },
  { color: [0, 255, 0], position: 33 },
  { color: [0, 255, 255], position: 50 },
  { color: [0, 0, 255], position: 67 },
  { color: [255, 0, 255], position: 83 },
  { color: [255, 0, 0], position: 100 },
];

In onSliderDrag, we loop over colorStops and find two consecutive color stops using thumbPosition, calculate the percentage based on these two color stops and then calculate the color (let's call this color-Z) at the position of the thumb, and set this color as the background of the main color palette.

When moving the thumb of the color-slider and let's assume the position of the thumb of the main palette is at the position (20%, 35%), at this time then changes color at the top-right corner of the main palette with color-Z that is calculated in the onSliderDrag function, so the color at the position of the thumb of the main palette also gets changed.

So, to calculate the color at the thumb of the main palette, we need to first calculate the color (let's call this color-X) horizontally on the upper edge gradient line with a percentage of 20/100, start color [255, 255, 255] (because it is the top-left corner, and here the color is always white) and the end color is color-Z.

Then finally, calculate the color at the thumb of the main palette with a percentage of 35/100, the start color is color-X, and the end color is [0, 0, 0] because this point is on the bottom edge line which is always black. Below is the rest of the code of the onSliderDrag function:

    if (color) {
      this.position = thumbPosition;
      this.color = color;
      let color2 = interpolateColors(
        interpolateColors([255, 255, 255], this.color, this.position2[0] / 100),
        [0, 0, 0],
        this.position2[1] / 100
      );
      this.color2 = color2;
      this.setChanges();
    }

Where I have checked the color for safety while it's almost always initialized with a color array. And, position and color are the thumb position and color at this position in the colors-slider, and position2 and color2 are the position of the thumb and color at this position in the main color palette.

The onSliderDrag function is called from the mouseMove or touchMove (for touch devices) event handler when dragging starts from the colors-slider, as shown in the below code:

  mouseMove = (event) => {
        const cx = event.clientX;
        let x = cx - this.sliding.getBoundingClientRect().left;
        if (x < 0) x = 0;
        if (x > this.sliding.clientWidth) x = this.sliding.clientWidth;
        const thumbPosition = (x * 100) / this.sliding.clientWidth;
        this.onSliderDrag(thumbPosition);
  };

Where 'sliding' is a reference to the color-slider element that is initialized when dragging starts and the 4th and 5th lines of code are used to ensure x is under range because the mouseMove event is attached to the document so that sliding can be possible even if the mouse drags out of the slider rect.

  onMainDrag = (x, y, w, h) => {
    let color = interpolateColors(
      interpolateColors([255, 255, 255], this.color, x / w),
      [0, 0, 0],
      y / h
    );
    if (color) {
      this.position2 = [(x * 100) / w, (y * 100) / h];
      this.color2 = color;
      this.setChanges();
    }
  };

Similarly, call the above onMainDrag function from the mouseMove or touchMove (for touch devices) event handler when dragging starts from the main-palette, as shown in the below code:

  mouseMove = (event) => {
        const cx = event.clientX,
          cy = event.clientY;
        let x = cx - this.sliding.getBoundingClientRect().left;
        let y = cy - this.sliding.getBoundingClientRect().top;
        if (x < 0) x = 0;
        if (x > this.sliding.clientWidth) x = this.sliding.clientWidth;
        if (y < 0) y = 0;
        if (y > this.sliding.clientHeight) y = this.sliding.clientHeight;
        this.onMainDrag(
          x,
          y,
          this.sliding.clientWidth,
          this.sliding.clientHeight
        );
  };

Now here, 'sliding' is a reference to the main-palette element that is initialized when dragging starts from the main-palette.

Until now, the above codes are used to select color when the user moves the thumb of the slider or palette. Similarly, you can also write code to handle dragging on the alpha-slider.

I know I have not provided or mentioned each and every code used in creating the color-picker because you can write or connect these codes to work as we want. But if you still have some problems, then visit the GitHub Repository or you can directly use its NPM package.

In the next blog (not published now), I will try to complete this series and we will learn how to calculate the position of thumbs when the color is given. This was a little difficult for me, but maybe not for you or you can try to write the rest of the code.

To write the rest of the code, I suggest going to the live color-picker, drag different thumbs and notice carefully what happened because this is the solution to writing the rest of the logic code. I hope you enjoyed reading this post. Happy Coding!

Published on Feb 12, 2024
Comments (undefined)

Read More