Generate Cartoon Avatars in NextJS

Milecia

Having an avatar as a profile picture is pretty common these days. There are plenty of tools and artists that can create anything you want so you have unlimited options. You can get as particular as you want and maybe even come up with something no one has seen before.

Of course, there's a way to generate all kinds of avatars with JavaScript. That's what we'll be doing in this post with faces.js. This library lets us generate avatars based on a number of factors so we can have a little fun with this.

Initial setup

We'll this cartoon avatar generator with Next. The whole app will be a form with a number of dropdowns the user can change to get different features for their avatar and then it'll immediately update the displayed avatar. Let's start by creating a new Next app with the following command:

1$ yarn create next-app --typescript

Follow the prompts and name the project cartoon-avatar. Then we need to install the faces.js package. We'll also be using the Material UI library to make the app look better from the beginning. So run the following command to install everything we need:

1$ yarn add facesjs @mui/material @emotion/react @emotion/styled

This is all we need to make this app work and look good. Now we can turn our attention to the code. We'll start by building the form since it will have the most moving pieces and it should be its own component.

Create the form

First, let's create a new folder inside the pages folder called components. Inside this new components folder, make a new file called Form.tsx. This will have a lot of code in it, but remember that it's only because we have a lot of options that we want to offer the user to customize their avatars. So in the Form.tsx file, add the following code:

1// Form.tsx
2import {
3 Container,
4 FormControl,
5 InputLabel,
6 MenuItem,
7 Select,
8 Stack,
9} from "@mui/material";
10import { Options } from "../index";
11
12interface FormProps {
13 options: Options;
14 updateAvatar: (options: any) => void;
15}
16
17const Form = ({ options, updateAvatar }: FormProps) => {
18 return (
19 <Container>
20 <form
21 style={{
22 marginBottom: "24px",
23 marginTop: "24px",
24 width: "800px",
25 }}
26 >
27 <Stack spacing={2}>
28 <FormControl variant="standard">
29 <InputLabel id="body">Body</InputLabel>
30 <Select
31 name="body"
32 id="body"
33 value={options.body.id}
34 onChange={(e) => updateAvatar(e)}
35 label="Body"
36 >
37 <MenuItem value="body">
38 <em>None</em>
39 </MenuItem>
40 <MenuItem value="body2">Type 1</MenuItem>
41 <MenuItem value="body3">Type 2</MenuItem>
42 <MenuItem value="body5">Type 3</MenuItem>
43 </Select>
44 </FormControl>
45 <FormControl variant="standard">
46 <InputLabel id="ear">Ear</InputLabel>
47 <Select
48 name="ear"
49 id="ear"
50 value={options.ear.id}
51 onChange={(e) => updateAvatar(e)}
52 label="Ear"
53 >
54 <MenuItem value="ear1">
55 <em>None</em>
56 </MenuItem>
57 <MenuItem value="ear2">Type 1</MenuItem>
58 <MenuItem value="ear3">Type 2</MenuItem>
59 </Select>
60 </FormControl>
61 <FormControl variant="standard">
62 <InputLabel id="eye">Eye</InputLabel>
63 <Select
64 name="eye"
65 id="eye"
66 value={options.eye.id}
67 onChange={(e) => updateAvatar(e)}
68 label="Eye"
69 >
70 <MenuItem value="eye13">
71 <em>None</em>
72 </MenuItem>
73 <MenuItem value="eye5">Type 1</MenuItem>
74 <MenuItem value="eye19">Type 2</MenuItem>
75 <MenuItem value="eye12">Type 3</MenuItem>
76 </Select>
77 </FormControl>
78 <FormControl variant="standard">
79 <InputLabel id="facialHair">Facial Hair</InputLabel>
80 <Select
81 name="facialHair"
82 id="facialHair"
83 value={options.facialHair.id}
84 onChange={(e) => updateAvatar(e)}
85 label="Facial Hair"
86 >
87 <MenuItem value="none">
88 <em>None</em>
89 </MenuItem>
90 <MenuItem value="fullgoatee4">Type 1</MenuItem>
91 <MenuItem value="loganSoul">Type 2</MenuItem>
92 <MenuItem value="sideburns3">Type 3</MenuItem>
93 </Select>
94 </FormControl>
95 <FormControl variant="standard">
96 <InputLabel id="glasses">Glasses</InputLabel>
97 <Select
98 name="glasses"
99 id="glasses"
100 value={options.glasses.id}
101 onChange={(e) => updateAvatar(e)}
102 label="Glasses"
103 >
104 <MenuItem value="none">
105 <em>None</em>
106 </MenuItem>
107 <MenuItem value="glasses1-primary">Type 1</MenuItem>
108 <MenuItem value="glasses2-primary">Type 2</MenuItem>
109 <MenuItem value="glasses2-black">Type 3</MenuItem>
110 </Select>
111 </FormControl>
112 <FormControl variant="standard">
113 <InputLabel id="hair">Hair</InputLabel>
114 <Select
115 name="hair"
116 id="hair"
117 value={options.hair.id}
118 onChange={(e) => updateAvatar(e)}
119 label="Hair"
120 >
121 <MenuItem value="blowoutFade">
122 <em>None</em>
123 </MenuItem>
124 <MenuItem value="curly">Type 1</MenuItem>
125 <MenuItem value="juice">Type 2</MenuItem>
126 <MenuItem value="parted">Type 3</MenuItem>
127 </Select>
128 </FormControl>
129 <FormControl variant="standard">
130 <InputLabel id="head">Head</InputLabel>
131 <Select
132 name="head"
133 id="head"
134 value={options.head.id}
135 onChange={(e) => updateAvatar(e)}
136 label="Head"
137 >
138 <MenuItem value="head8">
139 <em>None</em>
140 </MenuItem>
141 <MenuItem value="head14">Type 1</MenuItem>
142 <MenuItem value="head3">Type 2</MenuItem>
143 <MenuItem value="head10">Type 3</MenuItem>
144 </Select>
145 </FormControl>
146 <FormControl variant="standard">
147 <InputLabel id="nose">Nose</InputLabel>
148 <Select
149 name="nose"
150 id="nose"
151 value={options.nose.id}
152 onChange={(e) => updateAvatar(e)}
153 label="Nose"
154 >
155 <MenuItem value="nose6">
156 <em>None</em>
157 </MenuItem>
158 <MenuItem value="nose5">Type 1</MenuItem>
159 <MenuItem value="nose13">Type 2</MenuItem>
160 <MenuItem value="nose9">Type 3</MenuItem>
161 </Select>
162 </FormControl>
163 </Stack>
164 </form>
165 </Container>
166 );
167};
168
169export default Form;

The first thing we do is import some components from MUI and a type from the index.tsx file. Don't worry about that type yet because we'll get to it in a later section. After all the imports, we create another type for the props that get sent to the form. Finally, we write the return statement that has the form fields.

There are 9 option dropdowns with 3-4 variations each. The options and their variants are based on values directly from the faces.js library. You can play around with some of the other SVG values provided by the library if you want to add more variants or options. Each time a dropdown is changed, we call the updateAvatar function that gets passed as a prop.

This is everything for the form users will interact with. Now we can jump over to the part where we actually render the avatar based on the user's inputs.

Add the avatar

This is where we get to have some fun and see things in action. Open the index.tsx file in the pages folder and delete everything. Then add the following code:

1// index.tsx
2import type { NextPage } from "next";
3import Head from "next/head";
4import * as faces from "facesjs";
5import { useEffect, useState } from "react";
6import { Container } from "@mui/material";
7import Form from "./components/Form";
8
9export interface Options {
10 body: { id: string };
11 ear: { id: string };
12 eye: { id: string };
13 facialHair: { id: string };
14 glasses: { id: string };
15 hair: { id: string };
16 head: { id: string };
17 mouth: { id: string };
18 nose: { id: string };
19}
20
21const Home: NextPage = () => {
22 const [options, setOptions] = useState<Options>({
23 body: { id: "body2" },
24 ear: { id: "ear3" },
25 eye: { id: "eye5" },
26 facialHair: { id: "none" },
27 glasses: { id: "glasses2-black" },
28 hair: { id: "juice" },
29 head: { id: "head3" },
30 mouth: { id: "mouth5" },
31 nose: { id: "nose9" },
32 });
33
34 const face = faces.generate();
35
36 useEffect(() => {
37 faces.display("avatar", face, options);
38 }, [face, options]);
39
40 function updateAvatar(e: any) {
41 e.preventDefault();
42
43 const updatedOptions = {
44 ...options,
45 [e.target.name]: { id: e.target.value },
46 };
47
48 setOptions(updatedOptions);
49 }
50
51 return (
52 <div>
53 <Head>
54 <title>What Avatar Is Yours</title>
55 <meta
56 name="description"
57 content="uses faces.js to make random avatars"
58 />
59 <link rel="icon" href="/favicon.ico" />
60 </Head>
61
62 <main style={{ width: "100%" }}>
63 <Container>
64 <h1>{`What's your face?`}</h1>
65 <Form options={options} updateAvatar={updateAvatar} />
66 <div
67 id="avatar"
68 style={{ margin: "0 auto", height: "auto", width: "300px" }}
69 ></div>
70 </Container>
71 </main>
72 </div>
73 );
74};
75
76export default Home;

This is where we display the form and the avatar to users. We start by importing a few components and methods we need. You'll see the faces.js library and our custom form component to note a few things. Next, we define the Options type that we imported in the Form component. So we know what the faces.js library expects. Then we get into the Home component.

Inside the Home component, we start by setting an initial state for the options. Then we create a new instance of an avatar using the generate method provided by faces. After that, we have a useEffect hook to make update the avatar being displayed every time an option is updated. Next, there is the updateAvatar function that will get called each time a form value is changed.

We do a little object trick in this function to only update the value that has been changed instead of needing to update all of the values. Then we start adding components to the return statement here. The main things we return are the form and the <div> that the faces library targets in the display method called in the useEffect hook.

That's actually all we have to do for this app! Now you can play around with the avatars that get generated.

Finished code

You can check out all of the code in the qr-card folder of this repo. You can also check out the app in this Code Sandbox.

Conclusion

There are a lot of ways to practice your JavaScript skills. For example, how would you have handled that object that gets updated based on a dynamic value? These are the little things that make app building a little faster every time you go through the process. Except in this case, instead of worrying about what happens in production, you get to see if your experiments work by looking at your avatar.

Milecia

Software Team Lead

Milecia is a senior software engineer, international tech speaker, and mad scientist that works with hardware and software. She will try to make anything with JavaScript first.