Animated CSS loaders
Tags: css, animation, webdev, design
Published: 2021-06-12 11:46:27

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.

{% codepen https://codepen.io/jenning/pen/LYWJdWz default-tab=result %}
###### 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

```css
.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;
}
```

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.

```css
.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);
}
```

- 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:

```css
.loader {
  display: grid;
  place-items: center;
}
```

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](https://developer.mozilla.org/en-US/docs/Web/CSS/::before).

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](https://developers.google.com/web/updates/2015/05/the-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.
<br>
## 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!