Lumenaki Crystal Loader

TL;DR;

See the Pen Lumenaki Loader by Khanh Hua (@khanhhua) on CodePen.

Long Story

Skills involved

  • CSS3 animation
  • CSS3 transform
  • Basic geometry and math
  • Patience

How to build

The HTML structure

.loader
  .quadrant
    .quadrant-sub
      span  
    .quadrant-sub
      span  
  .quadrant
    .quadrant-sub
      span  
    .quadrant-sub
      span  
  .quadrant
    .quadrant-sub
      span  
    .quadrant-sub
      span  
  .quadrant
    .quadrant-sub
      span  
    .quadrant-sub
      span  

You may want to use .quadrant-sub:after to create pseudo elements instead of span. Anyways, let us analyze the structure. As you have observed:

  • The indicator rotates about its center
  • The diamonds rotate about 2 axes: one diagonal and its center
  • Each diamond has two separate sub-parts, only one of the two is visible at a time.

There is a reason behind the aforementioned structure. We will discuss after we explore the CSS declarations.

CSS

The most interesting aspect would be keyframe definitions for rotations. Each DOM (loader, quadrant and subpart) needs to associate with one and only animation, one and only one transformation.

  • The loader (.loader)
    @keyframes ani-loader {
      0% {
        transform: rotate(0deg);
      }
      69% {
        transform: rotate(0deg);
      }
      100% {
        transform: rotate(90deg);
      }
    }
  • The sub-parts (.quadrant-sub)
    @keyframes ani-quad1-ne {
      0% {
        transform: rotate3d(-1,1,0,0deg);
      }
      20% {
        transform: rotate3d(-1,1,0,0deg);
      }
      44% {
        transform: rotate3d(-1,1,0,90deg);
      }
      100% {
        transform: rotate3d(-1,1,0,90deg);
      }
    }

    @keyframes ani-quad1-nw {
      0% {
        transform: rotate3d(1,1,0,90deg);
      }
      44% {
        transform: rotate3d(1,1,0,90deg);
      }
      55% {
        transform: rotate3d(1,1,0,0deg);
      }
      100% {
        transform: rotate3d(1,1,0,0deg);
      }
    }
  • The quadrants (.quadrant)
    @keyframes ani-quad1-rotate {
      0% {
        transform: rotate(0deg);
      }
      52% {
        transform: rotate(0deg);
      }
      69% {
        transform: rotate(90deg);
      }
      100% {
        transform: rotate(90deg);
      }
    }

I have shown this crystal loader to a few interviewees and colleagues. Most of them assumed an over simplistic solution in terms of both CSS and HTML structure. The common mistake is how the animated property transform is understood. In my solution, there are 13 keyframes definitions while the often proposed figure is 4. To animate this loader, we need:

  • 1 keyframes definition for the entire block
  • 4 keyframes definitions for the .quadrant
  • 8 keyframes definitions for the .quadrant-sub

The take-away point is that CSS matrix transformation is basically an application of linear algebra. By linear, it means each point of our DOM elements is converted from its original position to the final destination. No matter how many translate, rotate, scale and skew you may apply for the transform property, browser will calculate one and only one matrix which represents the entire transformation. During animation/transition, the final destination is recalculated based on the original position - NOT the previously calculated value using this matrix.

Put that altogether, we now understand how this loader works:

  • To produce the diamond, one matrix is used for the .quadrant-sub spans.
  • To make the diamond flip around the diagonal, one matrix* is applied for .quadrant-sub.
  • To make the diamond rotate around its center, one matrix* is applied for the .quadrants. Their children are rotated also.
  • To make the whole block rotate, one matrix* is applied for the .loader.

*matrix value is an interpolated figure.

In other words, you cannot make the diamonds, by themselves, distorted and animated at the same time. You have to wrap them as many levels as the number of concurrent matrix values are used.

Your assignment

Can you make this Lumenaki Crystal Loader adapt to various sizes? How about making the loader fluid?

See Lumenaki - album 209 for the piece of code in production.