I have recently live coded a pure CSS random rainbow particle explosion. There’s a source in the middle of the screen, and rainbow particles shoot out with different speeds at different moments and then fade out. It might seem like the kind of thing that requires a lot of work and code, but it’s something I did quite quickly and with only 30 lines of SCSS.
This writeup is about showcasing one particularly cool trick: there’s just one set of keyframes used to move all those particles on the screen! There’s a second one that’s responsible for fading them. But, even though they end up in completely different positions on the screen, only one set of keyframes is in charge of that.
Let’s see how it all works!
Structure
There’s not much going on here. We just drop 400
particles into the body
element. I used Haml because I feel it provides the simplest way of looping that doesn’t even involve a loop variable I’m not going to need anyway. Note that it’s all down to personal preference. I tend to go with the preprocessor that gives me the result I want with the least amount of code because the less I need to write, the better. In this particular case, it happens to be Haml. But, at the end of the day, any preprocessor that lets you generate all the .particle
elements in a loop works just fine.
- 400.times do
.particle
Basic styles
The first thing we do is size our particles and absolutely position. I chose to make them 4x4
squares because I was going for a pixelated look.
$d: 4px;
.particle {
position: absolute;
width: $d; height: $d;
}
Random positioning on the screen
We loop through these 400
particles (note that the number in the @for
loop in the SCSS needs to be the same number as the one we’ve used in the Haml loop) and we translate them at random x,y
points on the screen, where x
is between 1vw
and 100vw
and y
is between 1vh
and 100vh
. random(100)
gives us random integers between 1
and 100
, both inclusive.
At the same time, we also give these particles different random backgrounds so we can see them. We pick the hsl()
format because it’s the most convenient one here. random(360)
covers the entire hue wheel giving us random values between 1
and 360
.
We then max out the saturation (100%
) and set for a lightness above 50%
(65%
in this case).
.particle {
/* same styles as before */
@for $i from 0 to 400 {
&:nth-child(#{$i + 1}) {
transform: translate(random(100)*1vw, random(100)*1vh);
background: hsl(random(360), 100%, 65%);
}
}
}
We can now see our particles distributed all over the screen:
See the Pen by thebabydino (@thebabydino) on CodePen.
They trigger scrollbars, so we set overflow: hidden
on the root element. We also give it a dark background
so we can see our light rainbow particles better:
html {
overflow: hidden;
background: #222;
}
This way, we get a pretty night sky:
See the Pen by thebabydino (@thebabydino) on CodePen.
Animation
The next step is to animate these particles, making them shoot out from the middle of the screen. This means our animation starts at the 50vw,50vh
point:
@keyframes shoot {
0% { transform: translate(50vw, 50vh); }
}
We don’t specify a final (100%
) keyframe. If this is isn’t specified, it gets automatically generated from the styles we have set on the elements we animate – in our case, these are the random translates, different for each and every particle.
We want the motion of the particles to be fast at first and then slow down, which means we need to use an ease out type of timing function. We can just go for the plain old ease-out
, or we can use a more advanced one (I go to easings.net for this). We then give our animation
a dummy duration of 3s
and make it repeat infinitely.
.particle {
/* same styles as before */
animation: shoot 3s ease-out infinite;
}
We get the following result:
See the Pen by thebabydino (@thebabydino) on CodePen.
The particles shoot out to different positions in the plane, just like we wanted. But they all animate at once, which isn’t what we want. So the first fix is to give each one of them a different, random animation duration between 1s
and 3s
within the loop:
.particle {
/* same styles as before */
@for $i from 0 to 400 {
$t: (1 + .01*random(200))*1s;
&:nth-child(#{$i + 1}) {
/* same styles as before */
animation-duration: $t;
}
}
}
This is much better:
See the Pen by thebabydino (@thebabydino) on CodePen.
Then we give each particle a random negative delay between 0%
and 100%
of its animation-duration
($t
) in absolute value:
.particle {
/* same styles as before */
@for $i from 0 to 400 {
$t: (1 + .01*random(200))*1s;
&:nth-child(#{$i + 1}) {
/* same styles as before */
animation-delay: -.01*random(100)*$t;
}
}
}
See the Pen by thebabydino (@thebabydino) on CodePen.
Finally, we don’t want the particles to just disappear, so we add a second animation (with the same duration and the same delay) to fade them out:
.particle {
/* same styles as before */
animation: shoot 0s ease-out infinite;
animation-name: shoot, fade;
}
@keyframes fade { to { opacity: 0; } }
We now have the final result!
See the Pen by thebabydino (@thebabydino) on CodePen.
Brilliant! Great process beautifully written. Well done.
What a nice idea!
Here’s a starfield: http://codepen.io/anon/pen/wgJQXx
Brilliant.
Wow, great stuff!
Cool way!
You can even keep the fade-out within the single set of keyframes if you set opacity: 0 on .particle and opacity: 1 on shoot
True. I separated them with the intent of allowing for different timing functions – one for the motion, a different one for the fade. But I eventually dropped that idea and stuck with the two sets of keyframes anyway.
Hi. This is great :D.
BTW, It may be nothing, but I think this is an error:
If I understand
random
well in sass documentation,random(100)
gives us random integers between 1 an 100, both inclusive. Not zero. Even if we consider «between» as «not including the interval limits» it’s also wrong, because it won’t include zero, but you also will leave out 100. Same applies torandom(360)
, of course.I know it’s a little tiny detail, but just in case you want to correct/clarify it, if I’m not wrong :). In any case, it doesn’t interfere with the obtained result.
Oopsie! Fixed now, thanks! I should have actually checked the documentation for
random()
, but never did and just assumed the result was in the[0, $limit)
interval.