Skip to content

Instantly share code, notes, and snippets.

@jedfoster
Last active March 1, 2024 10:29
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save jedfoster/7939513 to your computer and use it in GitHub Desktop.
Save jedfoster/7939513 to your computer and use it in GitHub Desktop.
JavaScript version of Sass' mix() function

I recently found myself needing to combine two colors. Sass has a great function for doing this, even allowing you to control the percentage of each color, but in my case the two colors needed to change every few seconds, so Sass was out of the question. Enter JavaScript.

Here's my JavaScript version of Sass' mix() function.

var mix = function(color_1, color_2, weight) {
  function d2h(d) { return d.toString(16); }  // convert a decimal value to hex
  function h2d(h) { return parseInt(h, 16); } // convert a hex value to decimal 

  weight = (typeof(weight) !== 'undefined') ? weight : 50; // set the weight to 50%, if that argument is omitted

  var color = "#";

  for(var i = 0; i <= 5; i += 2) { // loop through each of the 3 hex pairs—red, green, and blue
    var v1 = h2d(color_1.substr(i, 2)), // extract the current pairs
        v2 = h2d(color_2.substr(i, 2)),
        
        // combine the current pairs from each source color, according to the specified weight
        val = d2h(Math.floor(v2 + (v1 - v2) * (weight / 100.0))); 

    while(val.length < 2) { val = '0' + val; } // prepend a '0' if val results in a single digit
    
    color += val; // concatenate val to our new color string
  }
    
  return color; // PROFIT!
};

Unlike the Sass version, my JavaScript version only accepts 6-digit hex values for the colors (#ff0000 works, but #f00 does not.) Like the Sass version, the weight argument is an integer value between 0 and 100.

var mixed = mix('ff0000', '0000bb', 75); // returns #bf002e

Because it only accepts 6-digit hex values you may need to convert your color from RGB, particularly if you obtained it using something like window.getComputedStyle(div).backgroundColor;, as I did. In that case, this function has you covered:

var rgb2hex = function(rgb) {
  // A very ugly regex that parses a string such as 'rgb(191, 0, 46)' and produces an array
  rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d\.]+))?\)$/);

  function hex(x) { return ("0" + parseInt(x).toString(16)).slice(-2); } // another way to convert a decimal to hex
  
  return (hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3])).toUpperCase(); // concatenate the pairs and return them upper cased
};

Use it like so:

var color1 = window.getComputedStyle(element1).backgroundColor; // 'ff0000'
var color2 = window.getComputedStyle(element2).backgroundColor; // '0000bb'

var mixed = mix(rgb2hex(color1), rgb2hex(color2), 75); // returns #bf002e

Play with the Sass version over on SassMeister.com

// ----
// Sass (v3.3.0.rc.1)
// Compass (v0.13.alpha.10)
// ----
body {
width: 100%;
height: 10em;
background: mix(#ff0000, #0000bb, 75);
}
body {
width: 100%;
height: 10em;
background: #bf002e;
}
@LukyVj
Copy link

LukyVj commented Mar 17, 2016

Kudos man, I decided to use this for a project I'm working on.
I just have a request, that could benefit to everyone.

Sometimes, the returned color looks like #NaN0718 or #NaNdad9, anyway, my point is, it would be cool to make it return the closest color before it returns a NaN.

So, if the color returned contains NaN, the script would return the last possible color before getting "NaNiffied" :)

@bobjones
Copy link

bobjones commented Jan 3, 2017

Thanks a TON!!!

We will be using this with one important change. We can't pass automation testing without the results being exactly the same as sass's values, so while this code creates visually identical colors, it will fail UAT tests (at least at my company). Changing Math.floor() to Math.round() makes the results the exact same as sass.

Consider the following test harness:

test-gray.scss

$lightest-gray: #f3f9fa;
$my-white: #ffffff;
.lightest-gray-88 { color: mix( $my-white, $lightest-gray, 88 ) };
.lightest-gray-91 { color: mix( $my-white, $lightest-gray, 91 ) };
.lightest-gray-94 { color: mix( $my-white, $lightest-gray, 94 ) };
.lightest-gray-97 { color: mix( $my-white, $lightest-gray, 97 ) };

sass values

Bobs-MacBook-Pro-2:ui-core bjones$ sass test-gray.scss 
.lightest-gray-88 {
  color: #fefefe; }
.lightest-gray-91 {
  color: #fefeff; }
.lightest-gray-94 {
  color: #feffff; }
.lightest-gray-97 {
  color: white; }

JS harness

var lightestGray = 'f3f9fa';
var myWhite = 'ffffff';
[ 88, 91, 94, 97 ].forEach( function( shade ) {
    console.log( '.lightest-gray-' + shade + ' {\n  color: ' + mix( myWhite, lightestGray, shade ) + '; }\n' );
});

JS values with Math.floor()

Bobs-MacBook-Pro-2:ui-core bjones$ node colorStyleGenerator.js 
.lightest-gray-88 {
  color: #fdfefe; }
.lightest-gray-91 {
  color: #fdfefe; }
.lightest-gray-94 {
  color: #fefefe; }
.lightest-gray-97 {
  color: #fefefe; }

JS values with Math.round()

Bobs-MacBook-Pro-2:ui-core bjones$ node colorStyleGenerator.js 
.lightest-gray-88 {
  color: #fefefe; }
.lightest-gray-91 {
  color: #fefeff; }
.lightest-gray-94 {
  color: #feffff; }
.lightest-gray-97 {
  color: #ffffff; }

@tenji73
Copy link

tenji73 commented Jun 7, 2018

nice one!!
i added

color_1 = color_1.replace(/#/g , '');
color_2 = color_2.replace(/#/g , '');

to accept colors with leading '#'..

@0N1rick
Copy link

0N1rick commented Oct 23, 2020

Thank you very much for this code!
And thank you @tenji73 for the '#' removal code!

@Dragod
Copy link

Dragod commented May 10, 2021

Tried to use this function but getting a Nan. Anyone knows how to fix it?

test
{
color: $primary-color;
background-color: mix('ff0000', '0000bb', 75);
width: grid(3, 12, 1200);
}

background-color:#NaNb403;

@geanfarias
Copy link

hey guys, I have a doubt with an inconsistency.

I used the following configuration in sass:

background: mix(#FFFFFF, #CC092F, 80);

and generated this color:

background: #f5ced5;

When I tried to use the function in js it generated this color:
image

Any reason for this difference?

@bobjones
Copy link

bobjones commented Jan 5, 2024

@geanfarias I know this is over a year later, but yes, see my comment above. Use Math.round() instead of Math.floor().

@geanfarias
Copy link

thanx @bobjones

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment