Skip to content
animorio
IT
All components

The loading skeleton

Loading, Transitions, TextGSAPMediumCompressed weight: Demo 5.2 KBGSAP 28 KBFreeDownload the zip

A profile card that opens as a skeleton of grey bones with a single glint crossing them all; once loaded, each bone turns into its piece from the top down: the cover is uncovered as its hills rise, the avatar opens like an iris, the words are wiped in and the numbers count up.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The loading skeleton</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/hanken-grotesk@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the card: while it loads every piece is a grey bone, and one glint runs across all of them together. Loaded,
         each bone turns into its piece from the top down: the cover uncovers its hills, the avatar opens, the words
         are wiped in, the numbers count up and the button fills -->
    <div class="profile" data-profile>
      <article class="card is-loading" aria-busy="true" aria-label="Profile" data-card>
        <div class="card__cover bone" data-bone="cover">
          <svg class="card__hills" viewBox="0 0 360 132" preserveAspectRatio="none" aria-hidden="true">
            <rect width="360" height="132" fill="#ffe3cf"/>
            <path data-hill d="M0 70 C60 40 110 52 170 66 C230 80 290 44 360 58 V132 H0z" fill="#ffc6a6"/>
            <path data-hill d="M0 92 C70 70 130 84 190 92 C250 100 300 76 360 86 V132 H0z" fill="#f59b7a"/>
            <path data-hill d="M0 112 C80 96 150 106 220 112 C280 117 320 102 360 108 V132 H0z" fill="#d9715a"/>
          </svg>
        </div>
        <div class="card__body">
          <div class="card__row">
            <span class="card__avatar bone" data-bone="avatar"><span class="card__face" aria-hidden="true">EM</span></span>
            <button class="card__follow bone" type="button" data-bone="button"><span>Follow</span></button>
          </div>
          <h2 class="card__name"><span class="bone" data-bone>Elena Marchetti</span></h2>
          <p class="card__role"><span class="bone" data-bone>Illustrator in Bologna</span></p>
          <p class="card__bio">
            <span class="bone" data-bone>Draws maps of places that do not</span>
            <span class="bone" data-bone>exist yet. Ink, watercolour and far</span>
            <span class="bone" data-bone>too much tea.</span>
          </p>
          <dl class="card__stats">
            <div><dt class="bone" data-bone>Posts</dt><dd class="bone" data-bone data-count="128">128</dd></div>
            <div><dt class="bone" data-bone>Followers</dt><dd class="bone" data-bone data-count="14200" data-short>14.2k</dd></div>
            <div><dt class="bone" data-bone>Following</dt><dd class="bone" data-bone data-count="311">311</dd></div>
          </dl>
        </div>
      </article>
      <button class="reload" type="button" data-reload data-autoclic>
        <svg viewBox="0 0 24 24" aria-hidden="true" data-reload-icon><path d="M19.5 12a7.5 7.5 0 1 1-2.2-5.3M19.5 4.5v4.2h-4.2"/></svg>
        <span>Load again</span>
      </button>
      <p class="sr" role="status" aria-live="polite" data-said></p>
    </div>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

24 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The loading skeleton” from Animorio and fit it into my site

What it does

On a white background a card with round corners, while it loads: in place of the cover, the round avatar, the button, each line of words and the numbers there are pale grey bones, and a glint crosses them all together from left to right, as if they were cut from the same sheet. Once loaded, the bones turn into their pieces from the top down: the cover is uncovered from the left and its peach hills rise one after another, the avatar with the initials EM opens like an iris, the black Follow button appears, Elena Marchetti, Illustrator in Bologna and the three lines of the description are wiped in line by line, and the numbers 128, 14.2k and 311 count up from zero. Under the card, Load again spins its arrow, quickly puts the bones back from the left, the glint starts again and the card loads once more.

How it works

A veil for every piece

The script puts a veil (an i element) inside every piece marked with data-bone. The words sit in inline-block elements, so the veil takes their width, and it is a little thinner than the line; under the veil the words stay see-through until their turn.

A single glint

All the veils have the same gradient, twice as wide as the card, shifted by how far right the veil is (--x) and by where the glint is (--b): background-position is --b − --x − --cw. A tween takes --b from one side of the card to the other in SWEEP seconds, over and over.

From the top down

Once the card has loaded, the bones line up from the top and from the left, STAGGER seconds apart: each veil shrinks with scaleX towards the right in WIPE seconds, uncovering its piece from the left. The cover’s hills rise one after another, the words drop 5 pixels into place, the avatar’s veil fades and the face opens with a circle clip-path.

The numbers

Each number keeps the width of its final figure (taken at the start as min-width), so the veil and the row do not move while it counts; the count goes from zero to the figure with power3.out, and 14200 is written 14.2k.

Again

Load again puts the veils back from the left, one every 0.025 seconds, starts the glint again and after LOAD seconds uncovers the card once more. The card has aria-busy while it loads, and a line for screen readers says when it is ready.

Values to change

NameFileDefaultWhat it changes
LOADscript.js1.6The seconds the bones show before the card has loaded.
SWEEPscript.js1.15The seconds the glint takes to cross the card.
WIPEscript.js0.55The seconds a bone takes to turn into its piece.
STAGGERscript.js0.06The seconds between a piece and the next, from the top down.

Try them in the Playground

When to use it

For anything that comes from a server and keeps you waiting a moment: profiles, product cards, articles, lists of results, dashboards.

On phones

The card takes the width of the screen minus a margin; bones and glint adjust by themselves, because the script measures the veils again when the card changes size. Two quick taps on Load again do not zoom the page.

With reduced motion

No glint, no rising hills and no counting: the bones stay still and, once the card has loaded, they all fade together leaving the pieces in place.

Pitfalls

  • A glint for each bone, each one on its own, would be out of step: the gradient is one for all, twice as wide as the card, and each veil shifts it by how far right it sits; so the light passes as one.
  • Letters have ascenders and descenders that stick out of the line’s box, and under a veil thinner than the line they would peek out: the words stay see-through until their turn.
  • A number counting up from zero changes width at every figure: without a minimum width taken from the final figure, the veil and the row would jiggle.
  • The avatar has a white border setting it off the cover: its veil has the same border too, or the round bone would look bigger than the avatar.
  • A veil exactly as big as its piece lets a hairline of the colour underneath through at the edge, because of the edge smoothing: the veils of the cover and the button are one pixel bigger on each side.

Libraries

GSAP 3.15.0, Hanken Grotesk (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 5.2 KB; the libraries: GSAP 28 KB.

Testing

Tested on 3 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: Done

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All components