When doing front-end interface development, the need to change the color of the need to use a color selector. For this problem, the first thought, of course, is that H5 provides input color, which can be implemented. But not surprisingly, IE doesn’t support it. Also, Chrome is implemented differently from Firefox, as shown below:

On the left is chrome’s own implementation of a color picker control. On the right, Firefox introduces the color picker control that comes with the operating system. In view of these differences, it is necessary to implement a uniform color selector component, such as one that comes with chrome. The first thing you need to know about this component is the color model.

Color model

Our front-end development, the most common color model is RGB, namely red, green and blue color model, while the front-end browser can support only two color models: RGB and HSL color display. Among them, RGB model is to mix the value of three colors within the range of 0-255 into one color, which is more machine-friendly, but not suitable for intuitive color selection by people. As a result, common color pickers typically use two other models that are more oriented toward visual perception: HSV and HSL. For detailed knowledge of color models, see the basics of color models. Chrome’s own color picker is based on HSV.

Differences between HSV and HSL

HSV is based on H(Hue), S(saturation) and V(Value). HSL is based on H(Hue), S(saturation) and L(lightness). Hue is the same, the essence is a color value. In both models, hues are based on six main colors arranged at 60-degree intervals on a ring. These six main colors are: 0° red, 60° yellow, 120° green, 180° green, 240° blue, 300° magenta, 360° red. In front end layout design, the ring is often processed as a long square color block, segmented by linear gradient color, and the Angle is converted into a certain proportion, as shown below:

Both saturation and brightness are different. In order to facilitate understanding of color pickers, this paper summarizes simple descriptions as follows:

  • S saturation in HSV reflects the value of mixing white into hue color, presenting the change from white to hue (H) color;
  • S saturation in HSL reflects the value mixed with gray in hue color, presenting a change from gray to hue (H) color.
  • The V brightness in HSV reflects the transition from black to hue (H) color.
  • The L brightness in HSL reflects the transition from black to hue (H) color and then to white.

Hue: 0-360; Hue: 0-360; Hue: 0-360. Saturation and brightness are 0 ~ 100%. Specific implementation differences, we can continue to look at.

Implementation selector

hue

The principle of hue has been introduced above, HSV is the same as HSL. At the code level, it is also easy to implement, in the style of CSS and div, just a linear color gradient:

<div class="div-bar">
  <! -- Color column -->
  <div id="colorBar" class="color-bar"></div>
  <! -- Slider -->
  <div id="divSlider" class="div-slider"></div>
</div>
Copy the code
.div-bar {
  position: relative;
  width: 10px;
  height: 200px;
  margin-left:15px;
  cursor: pointer;
}
.color-bar {
  background: linear-gradient(180deg.#f00.#ff0 17%.#0f0 33%.#0ff 50%.#00f 67%.#f0f 83%.#f00);
  width: 10px;
  height: 200px;
  margin-left: 20px;
}
.div-slider{...width: 8px;
  height: 8px;
}
Copy the code

Here is the vertical color column, width and height of 10 * 200, if you need to change the width and height layout horizontally.

Calculate the slider position of the chromatic column

To move the slider to obtain position information, first need to listen to the mouse event on the hue column, as follows:

  colorBar.addEventListener('mousedown'.function(e) {
    setHueSlider(e)
    document.addEventListener('mousemove', setHueSlider)
  })
  document.addEventListener('mouseup'.function() {
    document.removeEventListener('mousemove', setHueSlider)
  })
Copy the code

The setHueSlider function deals with the position of the slider point on the hue column, and then calculates the ratio to get the hue value:

  const clientRect = colorBar.getBoundingClientRect()
  // Get the position information, colorBarHEIGHT -colorBarHEIGHT
  let yDiff = event.clientY - clientRect.top
  yDiff = yDiff > colorBarHEIGHT ? colorBarHEIGHT : (yDiff < 0 ? 0 : yDiff)
  // Set the position of the slider to prevent it from leaving the color column
  const yTop = yDiff < 9 ? 0 : (yDiff - 9)
  divSlider.style.top = yTop + 'px'

  // If the hue is within 360 degrees, calculate the corresponding ratio value, 0-360
  color.hue = Math.round((yDiff / colorBarHEIGHT) * 360 * 100) / 100 | 0
Copy the code

We can get the corresponding hue value by calculating the proportion of the slider position of the hue column.

Saturation and brightness

In addition to the hue column, we also need to set up a color panel based on saturation and brightness. This is where the above description of the difference between the two comes in handy. The color panel is generally a rectangular area. Take HSV as an example. As mentioned above, the saturation of HSV can be simplified as the color change from white to hue. Brightness shows the change from black to red. When we design the panel, we use a rectangular panel area:

  • If the background color of the panel is set to hue color – red;
  • Set the saturation from left to right, you can set the opacity change on white from left to right, that is, white on the left, to full transparency on the right, linear gradient;
  • Set the brightness from the bottom to the top so that it is black at the bottom and transparent at the top, with a linear gradient.

In this way, the color panel in HSV model can be simplified. In terms of specific implementation, it can be understood by the following figure:

Of course, saturation and brightness can be swapped horizontally or vertically. To set the color palette, use div+ CSS. There are several ways to implement the code. Here is a simple way to create a 300-by-200 color palette using only one div (other ways are to use two divs or use pseudo-classes, etc.) :

<div class="div-panel">
  <! -- Color palette -->
  <div id ="colorPanel" class="color-panel" style="background-color: #f00;"></div>
  <! - the slider - >
  <div id="divPicker" class="div-picker"></div>
</div>
Copy the code
.div-panel {
  position: relative;
  width: 300px;
  height: 200px;
  cursor: pointer;
  overflow: hidden;
}
.color-panel {
  position: relative;
  height: 200px;
  width: 300px;
  background: linear-gradient(to top, # 000, transparent), linear-gradient(to right, #FFF, transparent);
}
.div-picker{...width: 12px;
  height: 12px;
}
Copy the code

Compute these two values

Still need to monitor the mouse events, obtain the mouse position information, event monitoring and hue are basically the same, are monitoring the three events of the mouse, skipped here. Let’s directly look at how to calculate saturation and brightness. According to the previous introduction and layout implementation, we define the horizontal X axis of the panel as saturation and the vertical Y axis of the panel as brightness, so we can calculate as follows:

  // calculate mouse movement position, panelWIDTH panelWIDTH, height panelHEIGHT
  const clientRect = colorPanel.getBoundingClientRect()
  let yDiff = event.clientY - clientRect.top
  let xDiff = event.clientX - clientRect.left
  xDiff = xDiff > panelWIDTH ? panelWIDTH : (xDiff < 0 ? 0 : xDiff)
  yDiff = yDiff > panelHEIGHT ? panelHEIGHT : (yDiff < 0 ? 0 : yDiff)

  // Set the position of the slider so that at least half of the slider is inside the panel
  const yTop = yDiff - 6
  const xLeft = xDiff - 6
  divPicker.style.top = yTop + 'px'
  divPicker.style.left = xLeft + 'px'

  // Saturation and lightness, there is no percentage value, need to pay attention to the subsequent conversion
  color.saturation = Math.round(xDiff / panelWIDTH * 100)
  color.value = Math.round((1 - yDiff / panelHEIGHT) * 100)
Copy the code

HSV turn RGB

Through such calculation, we can obtain the three values corresponding to HSV, which can be converted into the corresponding RGB color value later.

  const hsvToRgb = function (hue, saturation, value) {
    saturation = saturation * 255 / 100 | 0
    value = value * 255 / 100 | 0

    if (saturation === 0) {
      return [value, value, value]
    } else {
      satVal = (255 - saturation) * value / 255 | 0
      ligVal = (value - satVal) * (hue % 60) / 60 | 0
      if (hue === 360) {
        return [value, 0.0]}else if (hue < 60) {
        return [value, satVal + ligVal, satVal]
      } else if (hue < 120) {
        return [value - ligVal, value, satVal]
      } else if (hue < 180) {
        return [satVal, value, satVal + ligVal]
      } else if (hue < 240) {
        return [satVal, value - ligVal, value]
      } else if (hue < 300) {
        return [satVal + ligVal, satVal, value]
      } else if (hue < 360) {
        return [value, satVal, value - ligVal]
      } else {
        return [0.0.0]}}}Copy the code

transparency

In the RGB\HSV\HSL three color models, transparency is an independent property, does not affect the implementation of the specific color, can handle this value separately. So, if we need transparency, we can use the same layout as the color column, using a long div, and calculate the current selected transparency value by calculating the position ratio. The layout is as follows:

  <div class="div-alpha">
    <! -- Transparent columnar -->
    <div id="alphaBar" class="alpha-bar" style="linear-gradient(180deg, #f00, transparent)"></div>
    <! - the slider - >
    <div id="divAlphaSlider" class="div-slider"></div>
  </div>
Copy the code
  .div-alpha {
    background-image: linear-gradient(
      45deg.#c5c5c5 25%,transparent 0,transparent 75%.#c5c5c5 0.#c5c5c5),linear-gradient(
      45deg.#c5c5c5 25%,transparent 0,transparent 75%.#c5c5c5 0.#c5c5c5);
  }
Copy the code

In terms of calculation, it is basically the same as the color column, but the transparency value is between 0 and 100:

color.alpha = Math.round(100 - yDiff / alphaBarHEIGHT * 100) / 100
Copy the code

Once you have the value of transparency, to get the color value, add transparency to RGBA or the HSLA.

Hsl-based color picker

The differences between HSV and HSL have been explained above, and depending on their characteristics, only a few changes are required to implement the SELECTOR for HSL. First of all, there is no difference between Hue and transparency. They are exactly the same and do not need to be changed. Secondly, we need to change the saturation and brightness of the color panel. HSL only needs to change the CSS style of the panel in the layout:

  .color-panel {
    position: relative;
    height: 200px;
    width: 300px;
    /* Horizontal X-axis saturation set to gray gradient, vertical Y-axis set to white to transparent to black */
    background: linear-gradient(to bottom, #fff 0%, transparent 50%, transparent 50%.# 000 100%), 
      linear-gradient(to right, # 808080 0%, transparent 100%);
  }
Copy the code

After changing the color palette, the HSL hue, saturation, brightness, and transparency values are obtained in the same way as in HSV mode. Note that the HSLA color obtained at this time needs to be converted from HSL to RGB if RGBA is to be used. See the basics of color model for the conversion function. To sum up, a color picker is completed.

The use of canvas

In addition to layout color pickers based on DIV + CSS, we can also use canvas to handle color pickers. In terms of layout, hue, panel and transparency are all replaced by Canvas. Since canvas can also use linear gradient, there is no problem in setting the UI layout of color. The design of the slider is basically the same, with only minor changes, because the way to get colors is different with canvas. In the canvas hue and panel, you can directly obtain the pixel corresponding to the canvas, and the pixel itself is an RGBA color value. So the advantage of this is that we can skip the conversion process and get the available color values directly. Of course, no matter div or Canvas, because the layout method and the way to obtain color value are different, the number of color value can be different, especially canvas method, which is related to the pixel point of the canvas itself. However, both ways can be more than a few million color values, fully meet our needs.