Hi! I want to share a component of the profile picture.
At the first glance, there is nothing special, but… we're going to do a lettered avatar, because not all users upload their profile picture, so, we need a placeholder.
I think you're familiar with this "lettered avatar" concept
As an example, I found these packages
- https://github.com/ipavlyukov/react-lettered-avatar
- https://github.com/hammadhere7/lettered-avatar#readme
But our component is more specific, and I don't see any need to use external libraries, because the component is pretty simple. So let's break it down.
The design
The design is pretty simple. There will be two states and another one meta
when the image is loading. This is how they look like:
Step 1. Image as a background-image
This is our step one we need to add two props here — first is the className
we will manage the size of the component with this prop, and the second is image
. I added it as a background-image
and not as background
because I manage other background props in component's css
. There will be enough background-size: cover
and background-position: center
// ProfilePic/index.tsx
import React from "react";
import styles from "./styles.module.css";
/////////////////////////////////
//////// TYPES AND PROPS ////////
/////////////////////////////////
export interface Props {
className?: string;
image?: string;
}
///////////////////////////////
///////// COMPOENENT //////////
///////////////////////////////
const ProfilePic: React.FC<Props> = (props) => {
return (
<div
className={`${styles.avatar} ${props.className ? props.className : ""}`}
style={{
backgroundImage: `url(${props.image})`
}}
>
</div>
);
};
ProfilePic.defaultProps = {
className: "",
image: ""
} as Partial<Props>;
export default ProfilePic;
Step 2. Generate random gradients
Oh, there are so many ways how to generate gradients. In my taste the best way is to take two primary colors from the picture when you upload it to a server, … but we don't have this opportunity, so we will use predefined pairs. Also, there is another way to generate fully random colors
There is one important condition for gradients — the gradient should always be the same for each user, but we can't store gradients in a DB, because we have old users as well, they should have the same functionality as new users. We can solve this by generating a hash from the user's name and then convert it into a color.
This is our function to make the gradient, basically the function returns one gradient pair from the pairs array. This approach has one disadvantage — the more users in a row you have, the more gradient pairs you need, otherwise you will see gradient duplicates.
// stringToGradient.ts
const gradients = [
["#FBB199", "#FFD12E"],
["#E2ECA6", "#77A3FA"],
["#A7A0EF", "#77A3FA"],
["#94BE39", "#FFF3B4"],
["#FFA4A4", "#737ABB"]
];
const stringToGradient = (string: string) => {
// trim the string, remove all spaces
const trimmedString = string.trim().replace(/\s/g, "");
// this is how we take the first letter. It works with emojies.
const startHash = trimmedString
.split("")
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
// Covert the hash number into the number we can
const gradient = startHash % gradients.length;
// and return the linear-gradient
return `linear-gradient(45deg, ${gradients[gradient][0]} 15%, ${gradients[gradient][1]} 90%)`;
};
export default stringToGradient;
We will add this function as the second image background and if there is no image, or it's still loading, a user will see the gradient.
// ProfilePic/index.tsx
import React from "react";
import stringToGradient from "./stringToGradient";
import styles from "./styles.module.css";
/////////////////////////////////
//////// TYPES AND PROPS ////////
/////////////////////////////////
export interface Props {
className?: string;
name: string; // pass it into `stringToGradient`
image?: string;
}
///////////////////////////////
///////// COMPOENENT //////////
///////////////////////////////
const ProfilePic: React.FC<Props> = (props) => {
//
return (
<div
className={`${styles.avatar} ${props.className ? props.className : ""}`}
style={{
backgroundImage: `url(${props.image}), ${stringToGradient(props.name)}`
}}
>
</div>
);
};
ProfilePic.defaultProps = {
className: "",
image: ""
} as Partial<Props>;
export default ProfilePic;
Step 3. Add a letter
There are some technics where devs generate letters with canvas
and then convert them into images. But I'm not the fan of the approach, because it's not scalable. You need to generate several images with the letter for different sizes or one universal, that can be heavy.
I think it's better to use vector — we can scale it, it has a small weight, and it's easier to maintain.
Here how we will do this. We can't use usual tags like span
, p
, because you can't scale them if you set the avatar size from 60px
to 120px
, your font-size: 16px
remains the same. But SVG text
tag can handle the magic of scaling. You set the default font-size
and then it will be scaled depending on the size of the parent, just set the size of SVG 100%
.
We will also need to align the text to the center. Just add textAnchor="middle"
and alignmentBaseline="middle"
to the text
and set the y
and x
. x,y
params can be relative from font to font, but for me the ideal center is x="50%"
and y="52%"
.
// ProfilePic/index.tsx
import React from "react";
import stringToGradient from "./stringToGradient";
import styles from "./styles.module.css";
/////////////////////////////////
//////// TYPES AND PROPS ////////
/////////////////////////////////
export interface Props {
className?: string;
name: string;
image?: string;
}
///////////////////////////////
///////// COMPOENENT //////////
///////////////////////////////
const ProfilePic: React.FC<Props> = (props) => {
//
return (
<div
className={`${styles.avatar} ${props.className ? props.className : ""}`}
style={{
backgroundImage: `url(${props.image}), ${stringToGradient(props.name)}`
}}
>
{!props.image && (
<svg className={styles.letterPlaceholder} viewBox="0 0 60 60">
<text x="50%" y="52%" textAnchor="middle" alignmentBaseline="middle">
{Array.from(props.name)[0].toUpperCase()}
</text>
</svg>
)}
</div>
);
};
ProfilePic.defaultProps = {
className: "",
image: ""
} as Partial<Props>;
export default ProfilePic;
And the final result 👉 codesandbox
Top comments (0)