Last week I created a firework effect with CSS. It is relatively simple (it only requires one HTML element per firework) and customizable (it uses CSS custom properties to customize colors, sizes, positions...)
Here is a demo of the effect (see in full screen):
Before I continue, let me add a couple of disclaimers: first, the following code is a simplified version of the original one, view the demo above for the full customizable code (but it may be a bit complex). And second, this is fun to develop demo, but it may not be the most efficient thing to do code-wise.
How it works
The idea is to have a small element with absolute-sized backgrounds placed in different relative positions (e.g., using percentages). The use of absolute and relative is essential here. Having absolute-sized backgrounds means that their size will not change depending on the size of the container, but their position is relative, so it will change (or give the impression of changing) when the container is resized.
The relative distance between the dots is the same at all times. But as the container grows, the absolute distance between them expands too-similarly to how real fireworks work.
Look at the circle on the right side pointed by an arrow. Its position is 100% horizontal and 50% vertical. It was the same when the container was little, but it looks like a lot as the container grows. Yet, it is still in the same relative position as before: 100% horizontal and 50% vertical.
The code
After a (short) description of how things will work, let's get our hands dirty with the code! A great way of lea is by doing. Feel free to follow the steps along (you will need to add some things by yourself, which will make your fireworks more unique.)
HTML
As mentioned above, the HTML part is straightforward: we just need one element for each firework:
<div class="firework"></div>
I made it a <div>
but it could be any other element. If we wanted to make it more accessible, we could add a role
of "img" to indicate that it is an image and an aria-label
with a short description: "animated cartoon of a firework." But we could argue that it's not needed as it's more presentational and is already empty. So, let's keep it simple for now.
CSS
We start by defining the basic styles for our firework: a simple block container absolute positioned on the screen:
.firework {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 0.5vmin;
aspect-ratio: 1;
background:
/* background intentionally blank */
;
background-size: 0.5vmin 0.5vmin;
background-repeat: no-repeat;
}
Let's see the properties and values little by little:
-
position: absolute;
: the element will be moved around the screen. Having an absolute position is ideal for that without affecting other elements. -
top: 50%; left: 50%;
: as a default, the firework will be at the absolute center. We can change that later (if you dug into the code already, you saw these changes from firework to firework.) -
transform: translate(-50%, -50%);
: this is important. By translating the element -50% horizontally and vertically, it will grow in all directions (instead of increasing towards the bottom right side.) when we change its size. -
width: 0.5vmin; aspect-ratio: 1;
: the element will be a tiny square. I used the vmin unit, so it's responsive, but you can use px, rem, or your favorite CSS unit. -
background-size: 0.5vmin 0.5vmin;
: this is important too. We set a fixed background size, so once we define the gradients (see below), they will always have the same size, independently of how big the container is. -
background-repeat: no-repeat;
: we don't want the gradients to repeat. Otherwise, we'd have too many, and it wouldn't look nice.
For the background, we are going to add a bunch of radial gradients like this one:
radial-gradient(circle, yellow 0.5vmin, #0000 0) 50% 0%
Note: because the
background-size
is squared, the keywordcircle
is not really needed for our demo. I added it in case I had different sized backgrounds.
This gradient will place a circle of size 0.5vmin in the center (50%) top (0%) of the firework element. Now, we will code many other gradients in different positions. Just make sure that you distribute them all across the container. Don't have an empty side while another is full. Try to add equally into all the quadrants (remember that when the container expands, a "sided" firework will look weird.) I added a total of 30 backgrounds (7 per quadrant). As a recommendation, avoid the corners if you can. They are "outliers" and make it look just "meh" when the animation is active.
And talking about animations… Let's define the animation! It will divide into two parts: the firework flying up into the sky (compressed) and the firework explosion.
@keyframes firework {
0% {
transform: translate(-50%, 60vh);
width: 0.5vmin;
opacity: 1;
}
50% {
width: 0.5vmin;
opacity: 1;
}
100% {
width: 45vmin;
opacity: 0;
}
}
It starts by moving the firework element outside of the bottom of the screen (translate(-50%, 60vh)
), and keep it small (width: 0.5vmin
) and visible (opacity: 1
). The transform specified in the element is translate(-50%, -50%)
. So the animation will translate the element from the bottom to its original (at 100%).
Halfway through the animation, we are keeping the element small and visible, and from then on, it will expand to its final size (45vmin
) and fuse with the background (by setting an opacity
of 0
).
.firework {
animation: firework 2s infinite;
...
}
Finally, I applied the same styles to the ::before
and ::after
pseudo-elements of the firework, and then made some minor modifications to both of them (rotating, scaling, using 3D rotation, you pick), so I had triple the number of particles (and in "different positions") without having to add any more backgrounds or elements:
.firework,
.firework::before,
.firework::after {
content: "";
...
}
.firework::before {
transform: translate(-50%, -50%) rotate(25deg) !important;
}
.firework::after {
transform: translate(-50%, -50%) rotate(-37deg) !important;
}
The angle of the rotate()
is random for both the ::before
and ::after
. The !important
is necessary to override the one from the animation. However, the effect may look cool without it, too. Feel free to try and experiment.
The result
After following these steps, we have a single line of HTML and 50–60 lines of CSS (depending on the number of radial gradients). The result will look like this:
Simpler than the one displayed above, but the code is also considerably simpler. Time to personalize it and make it your own!
Conclusion
As a final touch and to make things easier to edit (but a bit more complicated to follow), I replaced some of the values with some custom properties. The CSS variables make it easier for me to include other fireworks with minimal code changes. I didn't add that part to this article because I wanted to keep it simple, but you can see the variables on the demo's code at the beginning of the article.
I hope you liked the article. If you develop your own version or make changes to the version above, please share it. I'll enjoy checking it and exploring the code :)
Article image by Erwan Hesry on Unsplash.
Top comments (5)
Very cool! Using only CSS is quite an achievement!
Good job. Using css to generate a firework show is really cool.
👍👍👍
awesome!
Thanks :)