Animated CSS loaders

j3nnning

Jenning Ho

Posted on June 12, 2021

Animated CSS loaders

Let's talk about a set of CSS loaders / spinners that I've made recently. A little brief on the idea and the technical details behind them.

Here is the set of loaders and their source code, written in plain HTML & CSS. The idea is to make them readable to anyone with some CSS understanding and hopefully inspire you to make one yourself.

They are rearranged for better display, not sequentially ordered.

The brief

With this set, I wanted to do some motion animation with 2 dots mixed with more advanced and tricky animation using 3d perspective, that relies a bit on sleight of hand principle for the visual to work.

The first and the main constraint I have is to make these with single HTML element. This is so that they can be easily applied to projects without complicating the code too much, which makes them practical.

Secondly without applying SCSS or SASS, I can't be doing a lot of hardcoded values, or multiple nth elements with loop. I mostly mitigate this constraint by using CSS custom properties. CSS custom properties is different from SASS variables, in the sense that they are not static. They are dynamic based on context, which you'll see from their application in this set. A good example is --loader-size-half applied in loader--7. By only setting --loader-size, --loader-size-half being an abstract calc function based on --loader-size, will also update its value accordingly.

This set contains mostly square and circles, as such they are always same height and width. Instead of setting both width and height manually, I choose to use the relatively new CSS property aspect-ratio. By only setting the width value, and setting aspect-ratio value to 1 / 1, it turns the element into square. Finally, border-radius 50% will make any element that has equal width and height into circles.


The .loader class

.loader {
  --loader-size: calc(var(--block-size) / 2);
  --loader-size-half: calc(var(--loader-size) / 2);
  --loader-size-half-neg: calc(var(--loader-size-half) * -1);
  --light-color: rgba(255, 255, 255, 0.3);
  --dot-size: 5px;
  --dot-size-half: calc(var(--dot-size) / 2);
  --dot-size-half-neg: calc(var(--dot-size-half) * -1);

  display: block;
  position: relative;
  width: var(--loader-size);
  display: grid;
  place-items: center;
  color: white;
}
Enter fullscreen mode Exit fullscreen mode

This is the base class for all loaders. Within this class I've declared a few CSS custom properties that can be applied and reused in every loader.

.loader {
  --loader-size: calc(var(--block-size) / 2);
  --loader-size-half: calc(var(--loader-size) / 2);
  --loader-size-half-neg: calc(var(--loader-size-half) * -1);
  --light-color: rgba(255, 255, 255, 0.3);
  --dot-size: 5px;
  --dot-size-half: calc(var(--dot-size) / 2);
  --dot-size-half-neg: calc(var(--dot-size-half) * -1);
}
Enter fullscreen mode Exit fullscreen mode
  • base loader size is half of block size, the container
  • --loader-size-half is a calc function that calculates half of loader size, which can be updated in real time by setting loader size value.
  • --loader-size-half-neg is simply half of loader size turned negative by multiplying it with -1.
  • light color is meant to be a constant to be used in many places.
  • dot size is the size of each tiny white circle, is set to 5px.

The elements inside each loader are centered by default with this 2 lines:

.loader {
  display: grid;
  place-items: center;
}
Enter fullscreen mode Exit fullscreen mode

The color of all elements is also set in this base class with color: white, which sets the value of currentColor and be used in every other classes.

position: relative is set so that child elements' (::before and ::after pseudo elements) position: absolute are anchored to it.


The dots

They are made with each loader element's pseudo elements, ::before and ::after. With each HTML element, we actually have 2 child elements and 1 parent element to work with. A lot of the time we can create these presentational elements with pseudo elements. You can read more about them in MDN.

You will notice that most other elements are set to vmin value rather than px value, but dot is different. vmin values are directly tied to the viewport size, while px is not. Ideally the dot size should be vmin too, but why is it not?

This is mainly to make its position precise in relative the the line that each dots is "on". Ultimately when rendered on screen, the visuals are rendered in px and the smallest px on screen is 1px. The line is a static 1px size, and as such dot has to be too to make it "sit" on the line centered precisely. You can change dot's 5px value to any vmin value to see what I mean.

The loaders with dots can work with 4px ~ 6px sizes visually. So why 5px? So that it can be placed centered to the line. ie. 2px on top, 1px on the line centered, 2px at the bottom. This is the kind of calculation we should make to create pixel perfect layout.


The animation

A set of keyframes is declared for each loader despite some of them are the same set of keyframes to keep them separated and contained. Within each loader, the keyframes are reused by applying CSS custom properties. You can see them in action in loader 5, 6, 8, 9.

Most animation used in this set of loaders are simple translating (moving along the x, y, z axis) and rotating, but made interesting / unique via different easing and delay settings.

The subjects of each animation (ie. the dots) are actually doing the same motion, just one earlier than the other. This is achieved by simply setting a animation-delay to one of the dot. Instead of setting a positive delay, which will cause one of the element to pause for awhile before animating, I applied a negative delay. This allow the element to act earlier instead of later, and so skipping the initial pause while also creating the difference in delay.

When it comes to easing, I am not actually a wizard with bezier-curve calculation. I first have an idea of the outcome, ie. "snappy", "bouncy", "smooth, etc, then play with them in the devtool easing editor to come up with a suitable easing for the animation. ie. for loader 7, the effect is achieved by having an overly "bouncy" easing that allows the dot to go out of it's linear curve and comes back. While most other loaders simply have a "snappy" motion.

Conclusion

With this, I have shared my thoughts and some technical details behind creating this set of loaders. I hope you can pick up some css trickery from this and inspire you to make your own. Feel free to use any of this in your project too. Would make me happy to see actual application of this set. Happy crafting!

💖 💪 🙅 🚩
j3nnning
Jenning Ho

Posted on June 12, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related