Create a Fireworks Animation in React

Banner for a MediaJam post

Milecia

With the Fourth of July right around the corner, this seems like a great time to practice building some custom animations with fireworks! Sometimes you'll work on projects that need some advanced animations, like an e-commerce or gaming site, and it can take some time to get them to function as you expect.

That's why we're going to use anime.js to implement a simple fireworks animation that can be customized by users. You'll see how you can create some cool animations using CSS and this package.

Initial setup

We'll need to create a new React app to get started. Run this command to bootstrap the fireworks app:

1$ npx create-react-app fireworks --template typescript

Once the installation is complete, let's add a few packages we'll use throughout the app with this command:

1$ npm i animejs @types/animejs @mui/material @emotion/react @emotion/styled

Now let's jump into the first component we need to make.

Make the fireworks component

We're going to have two main components: one for the fireworks animation and one for the form users will be able to customize the fireworks with. Let's start with the fireworks component. In the src directory, create a new folder called components. Inside this new folder, add a file and name it Fireworks.tsx. This is where we'll have all of the code for the fireworks animation.

Go ahead and open that file and add the following code and then we'll go through it all:

1// Fireworks.tsx
2import Button from "@mui/material/Button";
3import Container from "@mui/material/Container";
4import anime from "animejs";
5import { useState, useRef, useEffect } from "react";
6import "../Fireworks.css";
7import CustomizeForm from "./CustomizeForm";
8
9export interface CustomizationProps {
10 color: string;
11 size: number;
12 duration: number;
13 sparkAmount: number[];
14 rows: number[];
15}
16
17const Fireworks = () => {
18 const [customizations, setCustomizations] =
19 useState <
20 CustomizationProps >
21 {
22 color: "bf6d20",
23 size: 450,
24 duration: 150,
25 sparkAmount: Array.from(Array(10)),
26 rows: Array.from(Array(3)),
27 };
28
29 const animation = useRef(null);
30
31 const handleClick = () => {
32 // @ts-ignore
33 animation.current.play();
34 };
35
36 useEffect(() => {
37 // @ts-ignore
38 animation.current = anime.timeline({
39 direction: "alternate",
40 duration: customizations.duration,
41 autoplay: false,
42 easing: "easeInOutSine",
43 });
44
45 // @ts-ignore
46 animation.current.add(
47 {
48 targets: `.dots li`,
49 translateX: anime.stagger(10, {
50 grid: [customizations.rows.length, customizations.sparkAmount.length],
51 from: "center",
52 axis: "x",
53 }),
54 translateY: anime.stagger(10, {
55 grid: [customizations.rows.length, customizations.sparkAmount.length],
56 from: "center",
57 axis: "y",
58 }),
59 rotateZ: anime.stagger([0, 90], {
60 grid: [customizations.rows.length, customizations.sparkAmount.length],
61 from: "center",
62 axis: "x",
63 }),
64 delay: anime.stagger(200, {
65 grid: [customizations.rows.length, customizations.sparkAmount.length],
66 from: "center",
67 }),
68 easing: "easeInOutQuad",
69 },
70 Math.random() * customizations.duration
71 );
72 }, [customizations]);
73
74 return (
75 <Container>
76 <CustomizeForm
77 customizations={customizations}
78 setCustomizations={setCustomizations}
79 />
80 <div className="player">
81 {customizations.rows.map((_, i) => (
82 <ul className="dots">
83 {customizations.sparkAmount.map((_, i) => (
84 <li
85 style={{
86 backgroundColor: `#${customizations.color}`,
87 height: `${customizations.size + Math.random() * 3}px`,
88 maxHeight: "100%",
89 maxWidth: "100%",
90 width: `${customizations.size}px`,
91 }}
92 key={i}
93 />
94 ))}
95 </ul>
96 ))}
97 <Button variant="contained" color="success" onClick={handleClick}>
98 Play
99 </Button>
100 </div>
101 </Container>
102 );
103};
104
105export default Fireworks;

We start off by importing everything we need. There's some stuff coming from third-party packages and we have a couple of imports we'll define as we go through this app. You'll notice there's a custom stylesheet and another component called CustomizeForm. We'll get to these a little later.

For now, we'll move on and define the types for the options users will be able to change for the animation. They'll be able to play with the duration of the animation, the color of the fireworks, how many there are, and how big they are. Next, we start defining what happens in the component.

We define a new state variable with some default values to define the initial layout of the fireworks. Then we define a reference to an element that the animation will render in. After that, the handleClick method is defined which will play the animation any time a button is clicked.

Now we're at the fun part. The useEffect hook is where we actually define the animation. We set animation.current to follow a timeline. You can see where that customizations state is being used to update the animation duration when a user changes it. Then we add an animation for each of the elements in the .dots class.

Next, we define what types of translations and rotations should happen to all of the fireworks elements. Every time the user updates a value, we'll be able to see the change in the fireworks.

Finally, we get to define the render method which has all of these elements we've been talking about. Again, we don't have the CustomizeForm component yet, but we will in just a bit. For the most part, we're making a grid the size that the user defines it. There are some CSS values that get changed dynamically and the play button. Then we're done with the Fireworks component!

Now let's define some styles for those classes we have.

Add the CSS

In the root of the app, add a new file called Fireworks.css. Then add the following code inside it:

1/* Fireworks.css */
2.player {
3 display: flex;
4 flex-direction: column;
5 justify-content: center;
6 align-items: center;
7}
8
9.dots {
10 display: flex;
11 align-items: center;
12 list-style: none;
13 height: 100px;
14 padding: 0;
15 margin-top: 30px;
16 width: 100%;
17}
18
19.dots li {
20 border-radius: 2px;
21 margin: 0 2px;
22}

These are the classes we defined on the grid in our Fireworks component. They clean up some container configurations that can be done with MUI, but can be easier to implement with plain CSS. Those were the only styles we needed so we're good to go here.

Get user input

Now let's make that CustomizeForm component that lets users make changes to the fireworks. In the components folder, add a new file called CustomizeForm.tsx. Add the following code to this file:

1// CustomizeForm.tsx
2import Container from "@mui/material/Container";
3import Stack from "@mui/material/Stack";
4import TextField from "@mui/material/TextField";
5import { CustomizationProps } from "./Fireworks";
6
7interface CustomizeFormProps {
8 customizations: CustomizationProps;
9 setCustomizations: (options: CustomizationProps) => void;
10}
11
12export default function CustomizeForm({
13 customizations,
14 setCustomizations,
15}: CustomizeFormProps) {
16 function updateFireworks(e: any) {
17 e.preventDefault();
18
19 const { color, size, duration, sparkAmount, rows } = customizations;
20
21 const newValues = {
22 color: e.currentTarget.color.value || color,
23 size:
24 e.currentTarget.size.value !== ""
25 ? Number(e.currentTarget.size.value)
26 : size,
27 duration:
28 e.currentTarget.duration.value !== ""
29 ? Number(e.currentTarget.duration.value)
30 : duration,
31 sparkAmount:
32 e.currentTarget.sparkAmount.value !== ""
33 ? Array.from(Array(Number(e.currentTarget.sparkAmount.value)))
34 : sparkAmount,
35 rows:
36 e.currentTarget.rows.value !== ""
37 ? Array.from(Array(Number(e.currentTarget.rows.value)))
38 : rows,
39 };
40
41 setCustomizations(newValues);
42 }
43
44 return (
45 <Container>
46 <form
47 onChange={(e) => updateFireworks(e)}
48 style={{ marginBottom: "24px", marginTop: "24px", width: "800px" }}
49 >
50 <Stack spacing={2}>
51 <TextField id="color" label="Color" variant="standard" name="color" />
52 <TextField id="size" label="Size" variant="standard" name="size" />
53 <TextField
54 id="sparkAmount"
55 label="Number of sparks"
56 variant="standard"
57 name="sparkAmount"
58 />
59 <TextField
60 id="rows"
61 label="Number of firework rows"
62 variant="standard"
63 name="rows"
64 />
65 <TextField
66 id="duration"
67 label="Duration"
68 variant="standard"
69 name="duration"
70 />
71 </Stack>
72 </form>
73 </Container>
74 );
75}

This is a basic form with some input fields that triggers a state change. We import the packages like usual and we also import a type from the Fireworks component to define the options users change. Then we define the props this component expects which are the state variable and method we made in the Fireworks component.

Next is the component itself. It looks like a lot more is happening than it really is. The updateFireworks function is getting the form values and defaulting to the existing customizations values if users do change them. Then it updates the state which will re-render the Fireworks elements with these new values.

At the end, we have the form with the five options users can change for their fireworks. Any time a value is changed, it triggers a form event to call the updateFireworks function with the new values. That's all for the user form! Now let's make the last update to show all of this work in the browser.

Update the App.tsx file

Open App.tsx and delete everything out of it. This is where we're going to show our fireworks and the form in action. Add the following code to the file:

1// App.tsx
2import Fireworks from "./components/Fireworks";
3
4function App() {
5 return <Fireworks />;
6}
7
8export default App;

Now all of that behind-the-scenes work will finally be shown to users. If you run the app with npm start, you should see something like this now:

finished app

That's it! Now we're done with this little fireworks animation and you can start diving deeper into this library.

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 several good animation packages out there for JavaScript apps like, three.js, velocity.js, and GSAP. It doesn't hurt to do a little research into all of them if you know you're going to be working with browser animations. Most of them have great documentation and examples so you should be able to get up and running with any of them.

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.