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.
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.
% 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)
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>
๐จโ๐ป 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);
}
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);
}
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;
}
}
๐ค 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;
}
}
โ 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);
}
}
โก๏ธ 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;
}
โฎ 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);
It makes my head spin !
๐ฅ Drum roll ... the final result ... TADA!
Top comments (0)