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));
};
janos
113k15 gold badges154 silver badges396 bronze badges
asked Jun 16, 2016 at 18:46
\$\endgroup\$
0

1 Answer 1

8
\$\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.

answered Jun 16, 2016 at 19:43
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.