DEV Community

Cover image for Profile picture component
Pavel Laptev
Pavel Laptev

Posted on

Profile picture component

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

Image description

As an example, I found these packages

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:

design preview


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;

Enter fullscreen mode Exit fullscreen mode

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.

gradients preview

// 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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

And the final result 👉 codesandbox

Top comments (0)