5
\$\begingroup\$

The following is a function that accepts a hex value (with or without a #, 3 or 6 character) and returns another hex value that is "closest" to it from a set of static hex values defined within the function. It involves a conversion to RGB.

How do I make this function more "elegant"? This is more of a question in general JS syntax and style, but I figured this was a good general example to present. I'd imagine I need to include some kind of nested functions, anonymous functions or closures that handle each part of the algorithm.

Here's the code, taking colors from Google's logo, for instance:

function nearestHex(hex) {

  var hexToRgb = function(hex) {
    var shortRegEx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
    hex = hex.replace(shortRegEx, function(full, r, g, b) {
      return [r, r, g, g, b, b].join();
    });
    var longRegEx = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?/i;
    var rgbArray  = longRegEx.exec(hex);
    var rgbObj = rgbArray ? {
      r: parseInt(rgbArray[1], 16),
      g: parseInt(rgbArray[2], 16),
      b: parseInt(rgbArray[3], 16)
    } : null;
    return rgbObj;
  }();

  var closestHexFromRgb = function(rgbObj) {
    if (!rgbObj) {
      throw new Error("The hex you provided is not formatted correctly. Please try in a format such as '#FFF' or '#DDFFDD'.");
    };

    var staticColors = [
      { color: 'red',    hex: '#EA4235', rgb: { r: 234, g: 66,  b: 53  } },
      { color: 'green',  hex: '#34A853', rgb: { r: 512  g: 168, b: 83  } },
      { color: 'blue',   hex: '#4285F4', rgb: { r: 66,  g: 133, b: 244 } },
      { color: 'yellow', hex: '#FBBC05', rgb: { r: 251, g: 188, b: 5   } }
    ];
    var minDistance = Number.MAX_SAFE_INTEGER;
    var nearestHex = null;

    for (var i=0; i<staticColors.length; i++) {
      var currentColor = staticColors[i];
      var distance = Math.sqrt(
        Math.pow((rgbObj.r - currentColor.rgb.r), 2) +
        Math.pow((rgbObj.g - currentColor.rgb.g), 2) +
        Math.pow((rgbObj.b - currentColor.rgb.b), 2)
      );
      if (distance < minDistance) {
        minDistance = distance;
        nearestHex = currentColor.hex;
      }
    };
    return nearestHex;
  }();

  closestHexFromRgb(hexToRgb(hex));

};
\$\endgroup\$
7
\$\begingroup\$

Fixing and improving the short regex handling

This won't work as intended:

hex = hex.replace(shortRegEx, function(full, r, g, b) {
  return [r, r, g, g, b, b].join();
});

The default separator of join is a comma, so this will join the hexadecimal digits by commas, when you want empty string: .join("")

In any case, instead of the anonymous function + array + joining, it will be simpler to use the capture groups of the regex in the replacement string:

hex = hex.replace(shortRegEx, "$1$1$2$2$3$3");

Don't repeat yourself

Instead of repeating parseInt(x, 16) multiple times here:

  r: parseInt(rgbArray[1], 16),
  g: parseInt(rgbArray[2], 16),
  b: parseInt(rgbArray[3], 16)

It would be better to create a function for it.

The same goes for this snippet in the distance calculation:

    Math.pow((rgbObj.r - currentColor.rgb.r), 2) +
    Math.pow((rgbObj.g - currentColor.rgb.g), 2) +
    Math.pow((rgbObj.b - currentColor.rgb.b), 2)

When defining the static colors here, it's not obvious that the rgb values are correct:

  { color: 'red',    hex: '#EA4235', rgb: { r: 234, g: 66,  b: 53  } },
  { color: 'green',  hex: '#34A853', rgb: { r: 512  g: 168, b: 83  } },
  { color: 'blue',   hex: '#4285F4', rgb: { r: 66,  g: 133, b: 244 } },
  { color: 'yellow', hex: '#FBBC05', rgb: { r: 251, g: 188, b: 5   } }

You could make it obvious by reusing the hexToRgb function to create the rgb values.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.