DEV Community

Cover image for ๐ŸŽจ Discover CSS Keyframes
Aymeric Le Feyer
Aymeric Le Feyer

Posted on

๐ŸŽจ Discover CSS Keyframes

The purpose of this article is to share how I solved this problem. Copying/pasting each block of code will not work.

Let's have fun with CSS !

๐Ÿ‘‹ This morning I gave me a challenge : making a custom spinner only with HTML and CSS (JS is allowed but only for creating a module for reusability)

๐Ÿ’ผ My objective was to animate the logo of Pit, the company in which I'm involved.

PIT Logo

First of all, the mockup

๐Ÿ’ก I had a general idea of โ€‹โ€‹what I wanted to do, so I decided to draw each step of the animation.

Animation steps

% Percentages you can see are the progression of the animation. Each frame lasts a different duration.

โฐ Here is the final timeline , with mathematics for calculating some areas (thank you Pitagore)

Timeline and calculus

Do you need a translation of the timeline ?

  • 0% -> 10% : Increased logo size
  • 10% -> 15% : Appearance of the square nยฐ2 (left one)
  • 15% -> 20% : Appearance of the square nยฐ3 (top one)
  • 20% -> 25% : Appearance of the square nยฐ1 (right one)
  • 25% -> 35% : Appearance of the "addons", small squares next to each big square
  • 35% -> 90% : Rotation (because it's a spinner !)
  • 90% -> 100% : Decreased logo size

Can you imagine the result ?

Now, let's code !

๐Ÿ› For the HTML, I wanted to have the small import possible of the spinner, because I can imagine use it for projects later.
I wrote the HTML first and forbade myself to edit it, because that's what I wanted it to look like.

<div
  id="pit-loader"
  data-color="#E9AE24"
  data-size="150"
  data-duration="3s">
</div>
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘จโ€๐Ÿ’ป Now, a pinch of JavaScript, I split the logo by 4 (yes, there are 4 squares).

const squareCount = 4;

for (var i = 0; i < squareCount; i++) {
  var squareNode = document.createElement("div");
  squareNode.classList.add("pit-square");
  loader.appendChild(squareNode);
}
Enter fullscreen mode Exit fullscreen mode

I used this way to keep the call of the spinner in HTML as simple as possible.

๐Ÿ‘ฉโ€๐ŸŽจ The core of the animation is in the CSS.
The logo is composed by 4 squares, and for each square, another small square. The logo is easy to reproduce, that's why I draw it in CSS

#pit-loader {
    position: absolute;
    display: flex;
    flex-wrap: wrap;
    gap: calc(var(--gap) * 1px);
    width: calc(var(--canvas) * 1px);
    height: calc(var(--canvas) * 1px);
    transform: rotate(225deg);
}

.pit-square {
    position: absolute;
    width: calc(var(--square) * 0.4px);
    height: calc(var(--square) * 0.4px);
    border: calc(var(--square) * 0.3px) solid var(--primary);
}

.pit-square::after {
    content: "";
    position: absolute;
    width: calc(var(--square) * 0.3px);
    height: calc(var(--square) * 0.3px);
    background-color: var(--primary);
    bottom: calc(var(--square) * 0.4px);
    right: calc(var(--square) * 0.4px * 2);
}
Enter fullscreen mode Exit fullscreen mode

Basically, this creates the logo you see before, nothing is relevant here.

๐ŸŽž To animate the logo, I used CSS Keyframes (Who guessed it ?) And it's now my split with percentages is useful !

๐Ÿž When we focus on the whole logo, the "only" animation is the size and the spinning, that's it !

@keyframes loader {
    0% {
        opacity: 0;   
        transform: scale(0); 
    }
    10% {
        opacity: 1;
        transform: scale(1);
    }
    35% {
        transform: rotate(225deg);
    }
    80% {
        transform: rotate(-135deg);
        opacity: 1;
    }
    100% {
        transform: rotate(-135deg) scale(0);
        opacity: 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿค” However the difficulty is the animation of the 4 squares, because I wished every square to appears after the previous one. So I created 4 keyframes

/* Animation of the right square */
@keyframes square-1 {
    0% {
        opacity: 0;
        right: 0;
    }
    20% {
        opacity: 0;
        right: 0;
    }
    25% {
        opacity: 1;
        right: calc((var(--square) + var(--gap)) * 1px);
    }
}

/* Animation of the left square */
@keyframes square-2 {
    0% {
        opacity: 0;
        left: 0;
    }
    10% {
        opacity: 0;
        left: 0;
    }
    15% {
        opacity: 1;
        left: calc((var(--square) + var(--gap)) * 1px);
    }
}

/* Animation of the top square */
@keyframes square-3 {
    0% {
        opacity: 0;
        top: 0;
    }
    15% {
        opacity: 0;
        top: 0;
    }
    20% {
        opacity: 1;
        top: calc((var(--square) + var(--gap)) * 1px);
    }
}

/* Animation of the bottom square */
@keyframes square-4 {
    0% {
        opacity: 0;
    }
    10% {
        opacity: 1;
    }
}
Enter fullscreen mode Exit fullscreen mode

โž• Finally the "addon" square is so easy

@keyframes addon {
    0% {
        opacity: 0;
    }
    25% {
        opacity: 0;
        right: 0;
    }
    35% {
        opacity: 1;
        right: calc(var(--square) * 0.4px * 2);
    }
}
Enter fullscreen mode Exit fullscreen mode

โžก๏ธ To give a specific keyframe to each square, I used the nth-child() way. Example for the right square.

.pit-square:nth-child(1) {
    top: calc(var(--square) * 1px + var(--gap) * 1px);
    animation: var(--animation-duration) ease-in infinite square-1;
}
Enter fullscreen mode Exit fullscreen mode

โฎ I talked before of data-attributes, I discovered them in this project, so here is a small explanation.
In HTML, we can add data-* attribute to any component, and retrieve them back in JS.

var loader = document.getElementById("pit-loader");
var color = loader.getAttribute("data-color");
loader.style.setProperty("--primary", color);
Enter fullscreen mode Exit fullscreen mode

It makes my head spin !

๐Ÿฅ Drum roll ... the final result ... TADA!

GIF result of the logo

Top comments (0)