Today let's work with CSS animations, delays and flex box in order to create a nice little ripple effect on a colored mosaic.
Read the full article or watch me code this on Youtube:
Result
Markup
The markup of this loader is quite simple as it consists of one wrapping div.mosaic-loader
and 16 div.cell
s. A templating engine like Pug can greatly help here, as it requires us to write only a few lines of code instead of copy'n'pasting a lot of them.
- const cells = 4;
.mosaic-loader
- for (let i = 0; i < cells; i++)
- for (let j = 0; j < cells; j++)
div(class='cell d-' + (i + j))
The expanded HTML looks like this. note that every 4 lines the d-n
class starts with a lower value. This comes from the i+j
as it sums up the row and the column index and therefore creates the diagonal symmetry. This is crucial for correctly managing the animation delays later.
<div class="mosaic-loader">
<div class="cell d-0"></div>
<div class="cell d-1"></div>
<div class="cell d-2"></div>
<div class="cell d-3"></div>
<div class="cell d-1"></div>
<div class="cell d-2"></div>
<div class="cell d-3"></div>
<div class="cell d-4"></div>
...8 more with d2-5 and d3-6
</div>
Basic CSS
The basic CSS code on one hand consists of the setup of the wrapper and on the other hand of the setup for each cell. In the beginning some CSS custom properties like --cell-size
are defined, as they allow us to easily customize the loader's appearance for each use case. Go ahead, give it a try and play with the values in the embedded code pen.
The wrapper itself is a flex
container that allows its child elements to be wrapped. By calculating the --total-size
as the width each of the elements in one row need and setting this as the wrapper's width, it ensures the square shape of the entire loader.
.mosaic-loader {
--cell-size: 64px;
--cell-spacing: 0px;
--border-width: 1px;
--cells: 4;
--total-size: calc(var(--cells) * (var(--cell-size) + 2 * var(--cell-spacing)));
--cell-color: white;
display: flex;
width: var(--total-size);
height: var(--total-size);
flex-wrap: wrap;
For each cell, the flex
configuration is setup in a way such that it can neither shrink nor grow (0 0
) and always remains at a flex base (width in this case) of --cell-size
.
.mosaic-loader {
...
> .cell {
flex: 0 0 var(--cell-size);
height: var(--cell-size);
margin: var(--cell-spacing);
box-sizing: border-box;
background-color: transparent;
border: var(--border-width) solid var(--cell-color);
}
}
The animation itself
Now, for the animation itself it is setup to run for 1.5s and infinitely often. The keyframes are configured such that from 0% to 30% the background-color
fades from transparent to --cell-color
and back to transparent until 60%. So the rest of the 40% of the animation time it stays transparent in order to give it some time to overlap with the others.
.mosaic-loader {
> .cell {
animation: 1.5s ease ripple infinite;
animation-delay: 0s;
}
}
@keyframes ripple {
0% {
background-color: transparent;
}
30% {
background-color: var(--cell-color);
}
60% {
background-color: transparent;
}
100% {
background-color: transparent;
}
}
Managing the delays
As indicated in the beginning, to produce the actual ripple effect it is the delays between the diagonals need to be managed correctly. The number of different delays is calculated by the number of cells in on row multiplied by two minus two. And between each step the delay is increased by 100 milliseconds, such that it produces some temporal overlapping across the neighboring diangonals.
.mosaic-loader {
> .cell {
$delays: (2 * 4) - 2;
@for $i from 1 through $delays {
&.d-#{$i} {
animation-delay: $i * 100ms;
}
}
}
}
Making it colorful
And finally, to make it cheerfully colorful, just create an array of 16 colors and apply them via the :nth-child
selector. The nice thing is, that by using the --cell-color
CSS custom property, the color for each cell can be overridden individually.
.mosaic-loader {
> .cell {
$colors: (
...16 colors of your choice
);
@for $i from 1 through length($colors) {
&:nth-child(#{$i}) {
--cell-color: #{nth($colors, $i)};
border-color: var(--cell-color);
}
}
}
}