CSS Loading Animation - Ripple Effect in Mosaic (keyframes, flex box)
Tags: tutorial, css, webdev, ui
Published: 2021-01-03 14:59:40

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:

{% youtube GmjnDw_DQD8 %}

# Result

{% codepen https://codepen.io/crayon-code/pen/eYdVLJo %} 

## 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.

```pug
- 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.

```html
<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.

```scss
.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`. 

```scss
.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.

```scss
.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.

```scss
.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.

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