CSS Loading Animation Generator: From Keyframes to Performance Optimization#

A deep dive into CSS loading animations: keyframe principles, timing-function curves, GPU acceleration, and implementations of four classic spinner types.

Why Loading Animations Matter#

There’s a golden rule in UX: Never let users stare at a static screen while waiting. Loading animations don’t just ease waiting anxiety—they provide essential feedback that “the system is working.” A good loading animation needs three qualities: smooth, unobtrusive, and performant.

I recently built a tool site and needed a quick way to generate loading animations. After digging in, I found CSS animations have quite a few nuances worth exploring.

Four Classic Loading Animations Explained#

1. Circular Spinner#

The most common loading style, implemented with a simple border trick:

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #e5e7eb;  /* Gray base ring */
  border-top-color: #3b82f6;  /* Blue highlighted section */
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

Key insight: border-top-color colors only one edge, creating the “gap” effect. The linear timing-function ensures constant rotation speed, while infinite makes it loop forever.

2. Pulse Animation#

The pulse effect creates a breathing sensation through scale and opacity changes:

.spinner {
  width: 40px;
  height: 40px;
  background-color: #3b82f6;
  border-radius: 50%;
  animation: pulse 1s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% {
    transform: scale(0);
    opacity: 1;
  }
  50% {
    transform: scale(1);
    opacity: 0.5;
  }
}

Key insight: ease-in-out adds a natural “slow start, slow end” rhythm, feeling more organic than linear.

3. Bouncing Dots#

Three dots bouncing in sequence—the magic is in animation-delay:

.spinner {
  display: flex;
  gap: 6px;
}

.spinner::before,
.spinner::after,
.spinner {
  content: '';
  width: 10px;
  height: 10px;
  background-color: #3b82f6;
  border-radius: 50%;
  animation: bounce 1s ease-in-out infinite;
}

/* Staggered delays */
.spinner::before { animation-delay: 0s; }
.spinner { animation-delay: 0.16s; }
.spinner::after { animation-delay: 0.33s; }

@keyframes bounce {
  0%, 80%, 100% { transform: scale(0); }
  40% { transform: scale(1); }
}

Key insight: Using ::before and ::after pseudo-elements, a single HTML element generates three dots. Delays increment by 1/6 of total duration, creating a wave effect.

4. Bar Wave#

Five vertical bars stretching in sequence, commonly seen in video players:

.spinner {
  display: flex;
  gap: 4px;
  height: 40px;
  align-items: center;
}

.spinner div {
  width: 6px;
  height: 100%;
  background-color: #3b82f6;
  animation: stretch 1s ease-in-out infinite;
}

.spinner div:nth-child(2) { animation-delay: 0.1s; }
.spinner div:nth-child(3) { animation-delay: 0.2s; }
.spinner div:nth-child(4) { animation-delay: 0.3s; }
.spinner div:nth-child(5) { animation-delay: 0.4s; }

@keyframes stretch {
  0%, 40%, 100% { transform: scaleY(0.4); }
  20% { transform: scaleY(1); }
}

Key insight: scaleY scales only on the Y-axis, keeping width constant. Incrementing animation-delay creates the wave.

Deep Dive: Timing-Function#

The rhythm of CSS animations is controlled by timing-function:

Value Effect Best For
linear Constant speed Rotations
ease Fast-slow-slow General use
ease-in Slow start Exit animations
ease-out Slow end Enter animations
ease-in-out Slow at both ends Pulse, breathing

For fine-grained control, use cubic-bezier():

/* Custom curve with bounce */
animation: spin 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;

This curve creates a “bounce” effect because the second parameter -0.55 exceeds the 0-1 range.

Performance: GPU Acceleration#

Not all CSS property animations get GPU acceleration. Only transform and opacity are handled by the compositor—other properties trigger reflow or repaint.

/* Good: GPU accelerated */
.spinner {
  animation: spin 1s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Bad: Triggers reflow */
.spinner {
  animation: bad-spin 1s linear infinite;
}
@keyframes bad-spin {
  to { margin-left: 100px; }
}

Forcing GPU acceleration with will-change:

.spinner {
  will-change: transform;
  animation: spin 1s linear infinite;
}

But don’t overuse it—will-change consumes extra memory.

Accessibility#

Some users are sensitive to motion and may experience dizziness. Respect the prefers-reduced-motion media query:

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: none;
    /* Show static alternative */
    opacity: 0.5;
  }
}

Practical Tool#

If you need to quickly generate various loading animations, check out the JsonKit Loading Generator. It supports four animation types, custom size, color, and speed—one-click CSS code copy.


Written on 2026-05-05 21:58