Skip to content

Instantly share code, notes, and snippets.

@0x263b
Last active March 7, 2024 10:09
Show Gist options
  • Star 98 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save 0x263b/2bdd90886c2036a1ad5bcf06d6e6fb37 to your computer and use it in GitHub Desktop.
Save 0x263b/2bdd90886c2036a1ad5bcf06d6e6fb37 to your computer and use it in GitHub Desktop.
Random color from string in javascript

Random color from string in javascript

Consider a list of strings you need to permanently assign a random color.

First you should turn the string into a hash.

var string = "string"
var hash = 0

for (var i = 0; i < string.length; i++) {
	hash = string.charCodeAt(i) + ((hash << 5) - hash);
	hash = hash & hash;
}

console.log(hash) // -891985903
  • string.charCodeAt(i) returns the UTF-16 code for the character at index i
  • Bit operators work on 32 bits numbers. Any numeric operand in the operation is converted into a 32 bit number.
  • hash << 5 is equivalent to hash * Math.pow(2, 5) (hash * 32), except the bit operator << makes sure our result is a 32 bit number.
  • hash & hash again, makes sure we only return a 32 bit number.

Now we have something to play around with.

RGB

The simplest method is to turn our hash into an RGB string:

String.prototype.toRGB = function() {
    var hash = 0;
    if (this.length === 0) return hash;
    for (var i = 0; i < this.length; i++) {
        hash = this.charCodeAt(i) + ((hash << 5) - hash);
        hash = hash & hash;
    }
    var rgb = [0, 0, 0];
    for (var i = 0; i < 3; i++) {
        var value = (hash >> (i * 8)) & 255;
        rgb[i] = value;
    }
    return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
}

"string".toRGB() // rgb(17, 96, 213)

Or a hexadecimal string (fiddle)

String.prototype.toHex = function() {
    var hash = 0;
    if (this.length === 0) return hash;
    for (var i = 0; i < this.length; i++) {
        hash = this.charCodeAt(i) + ((hash << 5) - hash);
        hash = hash & hash;
    }
    var color = '#';
    for (var i = 0; i < 3; i++) {
        var value = (hash >> (i * 8)) & 255;
        color += ('00' + value.toString(16)).substr(-2);
    }
    return color;
}

"string".toHex() // #1160d5

The issue is this can potentially spit out any color value. Ideally we'd want to filter out values too similar to our background color, and some gray/bland colors.

Color from array

What if we hand pick some colors, and assign each string one of those? (fiddle)

String.prototype.toColor = function() {
	var colors = ["#e51c23", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#5677fc", "#03a9f4", "#00bcd4", "#009688", "#259b24", "#8bc34a", "#afb42b", "#ff9800", "#ff5722", "#795548", "#607d8b"]
	
    var hash = 0;
	if (this.length === 0) return hash;
    for (var i = 0; i < this.length; i++) {
        hash = this.charCodeAt(i) + ((hash << 5) - hash);
        hash = hash & hash;
    }
    hash = ((hash % colors.length) + colors.length) % colors.length;
    return colors[hash];
}

"string".toColor() // #e91e63

This method is better if we want to be very particular over what colors are allowed, but selecting a large number colors can get tedious.

HSL

How about we use the hash to pick a hue, then hardcode the intensity/lightness. (fiddle)

String.prototype.toHue = function() {
    var hash = 0;
    if (this.length === 0) return hash;
    for (var i = 0; i < this.length; i++) {
        hash = this.charCodeAt(i) + ((hash << 5) - hash);
        hash = hash & hash;
    }
	return hash % 360;
}

"string".toHue() // -223

Note: This can result in a negative value. CSS's hsl() handles values outside of 0..360 perfectly, but not everything does.

For the sake of engineering, lets expand this out a bit more to include range values. (fiddle)

String.prototype.toHSL = function(opts) {
    var h, s, l;
    opts = opts || {};
    opts.hue = opts.hue || [0, 360];
    opts.sat = opts.sat || [75, 100];
    opts.lit = opts.lit || [40, 60];

    var range = function(hash, min, max) {
        var diff = max - min;
        var x = ((hash % diff) + diff) % diff;
        return x + min;
    }

    var hash = 0;
    if (this.length === 0) return hash;
    for (var i = 0; i < this.length; i++) {
        hash = this.charCodeAt(i) + ((hash << 5) - hash);
        hash = hash & hash;
    }

    h = range(hash, opts.hue[0], opts.hue[1]);
    s = range(hash, opts.sat[0], opts.sat[1]);
    l = range(hash, opts.lit[0], opts.lit[1]);

    return `hsl(${h}, ${s}%, ${l}%)`;
}

"string".toHSL() // hsl(137, 97%, 57%)

"string".toHSL({
    hue: [-45, 45],
    sat: [75, 95],
    lit: [45, 55]
}) // hsl(2, 92%, 52%)
@Zarel
Copy link

Zarel commented Sep 6, 2016

Unfortunately, HSL's lightness doesn't tend to correspond well to how light or dark your eyes see colors.

The simple solution is to darken your yellows and cyans and lighten your blues.

The "right" solution is to use a color space that lets you specify luma, saturation, and hue. Something like http://vis4.net/blog/posts/avoid-equidistant-hsv-colors/

@KingAmada
Copy link

How do you reverse it to bring back original values?

@Yhoko
Copy link

Yhoko commented Jul 12, 2019

There's a hash function, so: you can't.

@quantuminformation
Copy link

totally awesome mate thx

@haywirez
Copy link

Thank you! Could be worth packaging 👾

@MaximeKoitsalu
Copy link

Very nice mate

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