Think for a moment about when you visit online discussion forums. Many of the users don't have any profile pic uploaded. Instead, there are just initials and background colors for the User Avatar.
And anytime you refresh, the background color remains the same.
Now take a second to think about how is this done.
There can be two straightforward ways to do it.
- You can store a background color, for each user in DB, and fetch every time with other user details OR
- Generate a unique (but consistent) background color based on some unique user attribute (like email, username, or userID).
Let's start on #2.
Generate random color
Let's first learn some basics of CSS Colors in the webpage. And we will follow the following journey to do this.
- Learn about CSS color formats
- Choose a color format
- Basics of string hashing
- Normalize hash
- Generate unique CSS color using Hash
CSS color Formats
From this MDN page, we can figure out the common CSS color formats:
- Named Colors
- red, green, blue, aqua
- RGB Hex
- #RRGGBB or #RGB
- #3b49df is blue, #333 is dark-grey
- rgb() function
- rgb(255, 255, 255) is white
- rgba() for additional alpha (transparency) value
- hsl() function
- hsl is Hue-Saturation-Lightness
- Hue - is for choosing color using the degree
- Saturation - less saturation is grey, more saturation is bright color
- Lightness - less lightness is black, more lightness is white
- hsla(), for additional alpha value There are some others, but we will stop here.
Choosing color format
Now, we can go with RGB and HSL both, but for the task at hand, HSL is comparatively easier to work with. ¯\_(ツ)_/¯ Why, you ask? Trust me...
And, you will find in end of article.
Creating string hash
Now we need to reduce a string to a simple number so that we can use that number to generate our color.
I'll dive deep into hashing in a different article (I mean, come-on... Computer Science community have dedicated huge time to create better hashing functions). But let's take an overview of what hash is.
From educative.io
Hashing is the process of converting a given key into another value. A hash function is used to generate the new value according to a mathematical algorithm. The result of a hash function is known as a hash value or simply, a hash.
Let's understand by an example and taking a string: Piyush Kumar Baliyan
. Don't take this to be a definitive article about hashing. I am still learning about hashing and only know the basics.
Attempt 1
const getHashOfString = (str: string) => {
return str.length;
}
console.log(getHashOfString('Piyush Kumar Baliyan')); //20
This is a super simple hash function, but it has very high chances of a conflict. (Conflict is when two different strings can generate the same hash).
Attempt 2
Lets modify the function to calculate the total of character code of each character in the string.
const getHashOfString = (str) => {
const charArray = Array.from(str);
return charArray.reduce((total, _char, index) => {
return total += str.charCodeAt(index);
}, 0);
}
console.log(getHashOfString('Piyush Kumar Baliyan')); // 1922
This is better hashing as it has fewer chances of conflict, but any string that has the same characters will result in conflict.
Attempt 3
Now, lets take total of charCode*index
.
const getHashOfString = (str) => {
const charArray = Array.from(str);
return charArray.reduce((total, _char, index) => {
return total += (str.charCodeAt(index) * index);
}, 0);
}
}
console.log(getHashOfString('Piyush Kumar Baliyan')); // 18329
This is better and has fewer chances of a conflict.
But this becomes a problem as this number (charCode*index) can become very large for large strings, and can still conflict Maths (╯°□°)╯︵ ┻━┻.
Attempt 4
Attempt 5
.
.
.
Attempt x-1
Little better, but still no-where near to the actual hashing algos like md5 and sha.
const getHashOfString = (str: string) => {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
hash = Math.abs(hash);
return hash;
};
console.log(getHashOfString('Piyush Kumar Baliyan')); // 3206952792
Normalize Hash
Now we have a hashing algo, but it returns any number, and we need numbers like these:
- Hue 0-360
- Saturation 0-100
- Lightness 0-100
So let's create a function normalize
to get the hash number to within our range.
const normalizeHash = (hash: number, min: number, max: number) => {
return Math.floor((hash % (max - min)) + min);
};
const h = normalizeHash(myHash, 0, 360);
const s = normalizeHash(myHash, 0, 100);
const l = normalizeHash(myHash, 0, 100);
Generate Unique Color
Now, we simply create a string using our h,s,l
values.
const hRange = [0, 360];
const sRange = [0, 100];
const lRange = [0, 100];
const generateHSL = (name: string): HSL => {
const hash = getHashOfString(name);
const h = normalizeHash(hash, hRange[0], hRange[1]);
const s = normalizeHash(hash, sRange[0], sRange[1]);
const l = normalizeHash(hash, lRange[0], lRange[1]);
return [h, s, l];
};
const HSLtoString = (hsl: HSL) => {
return `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`;
};
Here is what the output looks like:
That's it!?
If you see this can generate colors that are too harsh (saturated), or just too grey. And can generate colors that are too white or just too dark.
And this was the reason to use HSL. In RGB, the individual values or r,g,b makes the illuminition (lightness) of a color, and it is little difficult to control.
Just do this to get better color generation.
const hRange = [0, 360];
const sRange = [50, 75];
const lRange = [25, 60];
Now, play around with the below JsFiddle, and see how the saturation and lightness ranges affect the color generation.
- Use the light/dark theme to see which color ranges look good on respective theme.
- Drag the saturation to left, see how colors turn grey
- Drag the lightness to right, see how the colors become white (and reverse - black).
- Use range, to find out the best saturation and lightness for your brand.
Bonus tip
Use the hue limiter to generate colors like all green tones, or all purple tones, to better match with your brand colors.
Next Time
I am gonna write about how to generate User Avatars... NFT style.
Top comments (2)
This was extremely helpful. I wrote a Ruby on Rails View Component to generate an initials avatar using this technique.
Thanks so much.
gist.github.com/Yardboy/1f28d12b79...
Awesome stuff thanks for sharing