CSS Skeleton Loading πŸ’€

CSS Skeleton Loading πŸ’€

Published: 9/11/20203 min read
HTML
CSS
JS

Skeleton loading is a strategy/technique for improving user experience. In this post I want to share an example of how I would approach it without any UI libraries or fancy components.

Basically, skeleton loading is aimed at components or content areas that are being fetched from a backend or an API. We can use a loader for the entire page or even individual components, but this approach sometimes results in a flaky user experience. However, when applying skeleton loading we ensure that the basic structure of the page and its components are visible. Once our content is ready, we can remove the skeleton loaders and display the content.

Here is my skeleton loading example on codepen.io (hit rerun to see it in action):

A quick breakdown

For this example, I created a user card component that contains an avatar, name, email and contact button. The user card content is hard-coded for the sake of simplicity. In a real app or website you would probably fetch data and update the DOM with it.

<div class="user-card skeleton">
    <div class="user-cover">
      <img class="user-avatar" src="
        https://yossiabramov.com/images/avatar.jpeg" alt="user profile image" />
    </div>
    <div class="user-details">
      <div class="user-name hide-text">Yossi Abramov</div>
      <div class="user-email hide-text">[email protected]</div>
    </div>
    <button class="contact-user hide-text">CONTACT</button>
</div>

Notice that the .user-card element has a .skeleton class and every element that contains text has a .hide-text class. Now, this example is a bit CSS heavy so let’s go over the most important lines:

/* Skeleton */

/* Static Skeleton */

.user-card.skeleton .user-cover {
  background: #e2e2e2;
}

.user-card.skeleton .user-cover .user-avatar {
  display: none;
}

.user-card.skeleton .user-cover::after {
  content: "";
  position: absolute;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  left: 0;
  right: 0;
  margin: auto;
  bottom: -25px;
  border: 1px solid #fff;
  z-index: 10;
  background: #e2e2e2;
}

/* Animated Skeleton */

.user-card.skeleton .hide-text {
  background: #e2e2e2;
  color: transparent;
  position: relative;
  overflow: hidden;
}

.user-card.skeleton .hide-text::before {
  content: "";
  position: absolute;
  left: 0%;
  top: 0;
  height: 100%;
  width: 50px;
  background: linear-gradient(to right, #e2e2e2 25%, #d5d5d5 50%, #e2e2e2 100%);
  animation-name: gradient-animation;
  animation-duration: 2s;
  animation-iteration-count: infinite;
  filter: blur(5px);
}

@keyframes gradient-animation {
  from {
    left: 0%;
  }
  to {
    left: 100%;
  }
}

Basically, I have two states of skeleton loading: static and animated. The .user-cover and .user-avatar elements have a static skeleton – without any CSS transition or keyframe animation applied to them while all elements with the .hide-text class have a keyframe animation. The gradient-animation animation is applied to a ::before element that is positioned absolute to it’s "relative" .hide-text father. The animation is very simple but effective.

The JavaScript for this example only simulates a somewhat slow fetching of data. Once our data is fetched, we can remove our skeleton loaders.

const $el = document.querySelector(".user-card");

// Loading finished
setTimeout(() => {
  $el.classList.remove("skeleton");
  $el
    .querySelectorAll(".hide-text")
    .forEach((el) => el.classList.remove("hide-text"));
}, 3000);

Hope you find this approach to skeleton loading simple and clear πŸ™ .