Gravatar, or Globally Recognized Avatar, is a free service from WordPress that allows users to associate an avatar with their email address. Any site the user signed up to using the same email address can retrieve their Gravatar using a URL constructed from its MD5 hash.
Gravatar supports several options for fallback images for users that have not associated a Gravatar with their email:
However, all of these options are either static or completely randomized, so none of them are satisfactory if you want an image that’s easily identifiable as belonging to the user.
Luckily, we can take advantage of two properties of Gravatar to easily create our own customized fallbacks:
- There is a “blank” (transparent) fallback option that we can use to place our fallback behind the Gravatar itself. If the user has a Gravatar, this will display on top, hiding the fallback.
- MD5 hashes have high entropy (appearance of randomness) and consist entirely of hexadecimal digits.
We can therefore grab the first 6 hex digits of the MD5 hash, convert them to a background color, select a complementary dark/light text color to ensure adequate contrast, and superimpose the user’s initials over the top.
Stop talking and show me the code!
The data we have to work with is as follows:
export type UserData = {
name: string
emailMd5: string
}
For our purposes, we can assume emailMd5
is generated by our app’s back end, using something like Node.js’s crypto.createHash('md5').update(email).digest('hex')
.
Here’s our getColorAndBackground
function:
export const getColorAndBackground = (md5: string) => {
const matches = md5.match(/.{2}/g)!
const [red, green, blue] = matches.map(hex => parseInt(hex, 16))
// Formula from https://www.w3.org/TR/AERT/#color-contrast
const luminance = (red * 0.299 + green * 0.587 + blue * 0.114) / 255
const color = luminance > 0.6 ? '#222' : '#fff'
return {
background: `rgb(${[red, green, blue]})`,
color,
}
}
We also need a way of converting a full name into 1 to 3 initials:
export const getInitials = (name: string) => {
name = name.trim()
if (name.length <= 3) return name
return name
.split(/\s+/)
.map(w => [...w][0])
.slice(0, 3)
.join('')
}
This works well for names in languages that use spaces between words, and it also works well for Chinese names, which are not space delimited and typically consist of 2 or 3 characters.
Next, we need the Avatar
component itself, which can be rendered at different sizes depending on where it’s used. Here’s the component:
export const Avatar = ({
emailMd5,
name,
size = 50,
}: UserData & { size?: number }) => {
// 250px is large enough that it will suffice for most purposes,
// but small enough that it won't require too much bandwidth.
// We limit the minimum size to improve caching.
const url = `https://www.gravatar.com/avatar/${emailMd5}?s=${String(
Math.max(size, 250),
)}&d=blank`
const initials = getInitials(name)
const c = useStyles({ emailMd5, size, initials })
return (
<div className={c.parent}>
<div aria-hidden='true' className={c.swatch}>
{initials}
</div>
<img className={c.img} src={String(url)} alt={`${name}’s avatar`} />
</div>
)
}
Finally, we need the component styles. We use React-JSS to dynamically create unique class names and make use of the data passed in in from the Avatar
component:
const useStyles = createUseStyles({
parent: ({ emailMd5, size }) => ({
...getColorAndBackground(emailMd5),
position: 'relative',
width: size,
height: size,
borderRadius: '50%',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '5px 5px 10px rgba(0, 0, 0, 0.15)',
}),
swatch: ({ initials, size }) => ({
// scale the text size depending on avatar size and
// number of initials
fontSize: size / (1.4 * Math.max([...initials].length, 2)),
position: 'absolute',
fontFamily: 'sans-serif',
userSelect: 'none',
}),
img: ({ size }) => ({
position: 'absolute',
width: size,
height: size,
top: 0,
left: 0,
borderRadius: '50%',
}),
})
And that’s it! Here’s a selection of avatars generated using this method, with a couple of existing Gravatars thrown in for good measure:
You can explore or fork the finished item at the live CodeSandbox demo, which also includes a simple Card
component to show off what the avatars look like in context.