Category · Loading Animations Difficulty Level · Intermediate Published on · January 15, 2024

Loading Animations & Progress Indicators

A comprehensive collection of modern loading animations and progress indicators with smooth transitions, customizable styles, and performance optimization

#loading #progress #animations #spinners #indicators #ui

Responsive Design

Yes

Dark Mode Support

No

lines

11

Browser Compatibility

No

Live Preview

Interact with the component without leaving the page.

600px

Loading Animations & Progress Indicators

A comprehensive collection of modern loading animations and progress indicators featuring smooth transitions, customizable styles, performance optimization, and accessibility features for enhanced user experience.

Features

  • Multiple Animation Types: Spinners, bars, dots, waves, and custom shapes
  • Smooth Transitions: CSS-based animations with hardware acceleration
  • Progress Tracking: Real-time progress updates with percentage display
  • Customizable Styles: Easy theming and color customization
  • Performance Optimized: Lightweight animations with minimal CPU usage
  • Accessibility: Screen reader support and reduced motion preferences
  • Responsive Design: Adapts to all screen sizes and devices
  • Modern Design: Clean, minimalist aesthetics with subtle effects

Demo

<div class="loading-container">
  <!-- Spinner Animations -->
  <div class="animation-section">
    <h3>Spinner Animations</h3>
    <div class="spinner-grid">
      <!-- Classic Spinner -->
      <div class="spinner-item">
        <div class="spinner classic-spinner">
          <div class="spinner-circle"></div>
        </div>
        <span class="spinner-label">Classic</span>
      </div>

      <!-- Dots Spinner -->
      <div class="spinner-item">
        <div class="spinner dots-spinner">
          <div class="dot"></div>
          <div class="dot"></div>
          <div class="dot"></div>
        </div>
        <span class="spinner-label">Dots</span>
      </div>

      <!-- Pulse Spinner -->
      <div class="spinner-item">
        <div class="spinner pulse-spinner">
          <div class="pulse-ring"></div>
          <div class="pulse-ring"></div>
          <div class="pulse-ring"></div>
        </div>
        <span class="spinner-label">Pulse</span>
      </div>

      <!-- Wave Spinner -->
      <div class="spinner-item">
        <div class="spinner wave-spinner">
          <div class="wave-bar"></div>
          <div class="wave-bar"></div>
          <div class="wave-bar"></div>
          <div class="wave-bar"></div>
          <div class="wave-bar"></div>
        </div>
        <span class="spinner-label">Wave</span>
      </div>

      <!-- Orbit Spinner -->
      <div class="spinner-item">
        <div class="spinner orbit-spinner">
          <div class="orbit-center"></div>
          <div class="orbit-path">
            <div class="orbit-dot"></div>
          </div>
        </div>
        <span class="spinner-label">Orbit</span>
      </div>

      <!-- Square Spinner -->
      <div class="spinner-item">
        <div class="spinner square-spinner">
          <div class="square"></div>
          <div class="square"></div>
          <div class="square"></div>
          <div class="square"></div>
        </div>
        <span class="spinner-label">Square</span>
      </div>
    </div>
  </div>

  <!-- Progress Bars -->
  <div class="animation-section">
    <h3>Progress Bars</h3>
    
    <!-- Linear Progress Bar -->
    <div class="progress-item">
      <label class="progress-label">Linear Progress</label>
      <div class="progress-bar linear-progress" data-progress="65">
        <div class="progress-fill"></div>
        <div class="progress-text">65%</div>
      </div>
    </div>

    <!-- Gradient Progress Bar -->
    <div class="progress-item">
      <label class="progress-label">Gradient Progress</label>
      <div class="progress-bar gradient-progress" data-progress="80">
        <div class="progress-fill"></div>
        <div class="progress-text">80%</div>
      </div>
    </div>

    <!-- Animated Progress Bar -->
    <div class="progress-item">
      <label class="progress-label">Animated Progress</label>
      <div class="progress-bar animated-progress" data-progress="45">
        <div class="progress-fill">
          <div class="progress-shine"></div>
        </div>
        <div class="progress-text">45%</div>
      </div>
    </div>

    <!-- Step Progress -->
    <div class="progress-item">
      <label class="progress-label">Step Progress</label>
      <div class="step-progress">
        <div class="step completed">
          <div class="step-circle">
            <svg viewBox="0 0 24 24" fill="currentColor">
              <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
            </svg>
          </div>
          <div class="step-label">Step 1</div>
        </div>
        <div class="step-connector completed"></div>
        <div class="step completed">
          <div class="step-circle">
            <svg viewBox="0 0 24 24" fill="currentColor">
              <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
            </svg>
          </div>
          <div class="step-label">Step 2</div>
        </div>
        <div class="step-connector active"></div>
        <div class="step active">
          <div class="step-circle">
            <span>3</span>
          </div>
          <div class="step-label">Step 3</div>
        </div>
        <div class="step-connector"></div>
        <div class="step">
          <div class="step-circle">
            <span>4</span>
          </div>
          <div class="step-label">Step 4</div>
        </div>
      </div>
    </div>
  </div>

  <!-- Circular Progress -->
  <div class="animation-section">
    <h3>Circular Progress</h3>
    <div class="circular-grid">
      <!-- Basic Circular Progress -->
      <div class="circular-item">
        <div class="circular-progress" data-progress="75">
          <svg class="circular-svg" viewBox="0 0 100 100">
            <circle class="circular-bg" cx="50" cy="50" r="45"></circle>
            <circle class="circular-fill" cx="50" cy="50" r="45"></circle>
          </svg>
          <div class="circular-text">75%</div>
        </div>
        <span class="circular-label">Basic</span>
      </div>

      <!-- Gradient Circular Progress -->
      <div class="circular-item">
        <div class="circular-progress gradient-circular" data-progress="60">
          <svg class="circular-svg" viewBox="0 0 100 100">
            <defs>
              <linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
                <stop offset="0%" stop-color="#3b82f6"/>
                <stop offset="100%" stop-color="#1d4ed8"/>
              </linearGradient>
            </defs>
            <circle class="circular-bg" cx="50" cy="50" r="45"></circle>
            <circle class="circular-fill" cx="50" cy="50" r="45" stroke="url(#gradient1)"></circle>
          </svg>
          <div class="circular-text">60%</div>
        </div>
        <span class="circular-label">Gradient</span>
      </div>

      <!-- Multi-layer Circular Progress -->
      <div class="circular-item">
        <div class="circular-progress multi-layer" data-progress="85">
          <svg class="circular-svg" viewBox="0 0 100 100">
            <circle class="circular-bg" cx="50" cy="50" r="40"></circle>
            <circle class="circular-fill primary" cx="50" cy="50" r="40"></circle>
            <circle class="circular-bg secondary" cx="50" cy="50" r="30"></circle>
            <circle class="circular-fill secondary" cx="50" cy="50" r="30"></circle>
          </svg>
          <div class="circular-text">85%</div>
        </div>
        <span class="circular-label">Multi-layer</span>
      </div>
    </div>
  </div>

  <!-- Loading States -->
  <div class="animation-section">
    <h3>Loading States</h3>
    
    <!-- Button Loading States -->
    <div class="loading-states">
      <button class="btn-loading" id="loadingBtn1">
        <span class="btn-text">Submit</span>
        <span class="btn-spinner">
          <div class="mini-spinner"></div>
        </span>
      </button>

      <button class="btn-loading success" id="loadingBtn2">
        <span class="btn-text">Complete</span>
        <span class="btn-success">
          <svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
          </svg>
        </span>
      </button>

      <button class="btn-loading error" id="loadingBtn3">
        <span class="btn-text">Error</span>
        <span class="btn-error">
          <svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
          </svg>
        </span>
      </button>
    </div>

    <!-- Card Loading State -->
    <div class="loading-card">
      <div class="card-skeleton">
        <div class="skeleton-avatar"></div>
        <div class="skeleton-content">
          <div class="skeleton-line long"></div>
          <div class="skeleton-line medium"></div>
          <div class="skeleton-line short"></div>
        </div>
      </div>
    </div>
  </div>

  <!-- Interactive Demo -->
  <div class="animation-section">
    <h3>Interactive Demo</h3>
    <div class="demo-controls">
      <button class="demo-btn" data-action="start">Start Loading</button>
      <button class="demo-btn" data-action="progress">Simulate Progress</button>
      <button class="demo-btn" data-action="complete">Complete</button>
      <button class="demo-btn" data-action="reset">Reset</button>
    </div>
    
    <div class="demo-progress">
      <div class="progress-bar demo-bar" data-progress="0">
        <div class="progress-fill"></div>
        <div class="progress-text">0%</div>
      </div>
      
      <div class="demo-spinner" style="display: none;">
        <div class="spinner classic-spinner">
          <div class="spinner-circle"></div>
        </div>
        <span class="demo-status">Loading...</span>
      </div>
    </div>
  </div>
</div>
.loading-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 2rem;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.animation-section {
  margin-bottom: 3rem;
  padding: 2rem;
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
  border: 1px solid #e5e7eb;
}

.animation-section h3 {
  margin: 0 0 2rem 0;
  font-size: 1.5rem;
  font-weight: 700;
  color: #1a1a1a;
  text-align: center;
}

/* Spinner Animations */
.spinner-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 2rem;
  justify-items: center;
}

.spinner-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.spinner {
  width: 60px;
  height: 60px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.spinner-label {
  font-size: 0.875rem;
  color: #6b7280;
  font-weight: 500;
}

/* Classic Spinner */
.classic-spinner .spinner-circle {
  width: 100%;
  height: 100%;
  border: 4px solid #e5e7eb;
  border-top: 4px solid #3b82f6;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Dots Spinner */
.dots-spinner {
  display: flex;
  gap: 8px;
}

.dots-spinner .dot {
  width: 12px;
  height: 12px;
  background: #3b82f6;
  border-radius: 50%;
  animation: dotPulse 1.4s ease-in-out infinite both;
}

.dots-spinner .dot:nth-child(1) { animation-delay: -0.32s; }
.dots-spinner .dot:nth-child(2) { animation-delay: -0.16s; }

@keyframes dotPulse {
  0%, 80%, 100% {
    transform: scale(0.8);
    opacity: 0.5;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}

/* Pulse Spinner */
.pulse-spinner {
  position: relative;
}

.pulse-ring {
  position: absolute;
  width: 100%;
  height: 100%;
  border: 3px solid #3b82f6;
  border-radius: 50%;
  animation: pulseRing 2s ease-out infinite;
}

.pulse-ring:nth-child(2) { animation-delay: 0.5s; }
.pulse-ring:nth-child(3) { animation-delay: 1s; }

@keyframes pulseRing {
  0% {
    transform: scale(0);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 0;
  }
}

/* Wave Spinner */
.wave-spinner {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 40px;
}

.wave-bar {
  width: 6px;
  background: #3b82f6;
  border-radius: 3px;
  animation: waveStretch 1.2s ease-in-out infinite;
}

.wave-bar:nth-child(1) { animation-delay: -1.2s; }
.wave-bar:nth-child(2) { animation-delay: -1.1s; }
.wave-bar:nth-child(3) { animation-delay: -1.0s; }
.wave-bar:nth-child(4) { animation-delay: -0.9s; }
.wave-bar:nth-child(5) { animation-delay: -0.8s; }

@keyframes waveStretch {
  0%, 40%, 100% {
    height: 10px;
  }
  20% {
    height: 40px;
  }
}

/* Orbit Spinner */
.orbit-spinner {
  position: relative;
}

.orbit-center {
  width: 20px;
  height: 20px;
  background: #3b82f6;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.orbit-path {
  width: 100%;
  height: 100%;
  border: 2px solid transparent;
  border-radius: 50%;
  position: relative;
  animation: orbit 2s linear infinite;
}

.orbit-dot {
  width: 12px;
  height: 12px;
  background: #1d4ed8;
  border-radius: 50%;
  position: absolute;
  top: -6px;
  left: 50%;
  transform: translateX(-50%);
}

@keyframes orbit {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Square Spinner */
.square-spinner {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}

.square-spinner .square {
  width: 20px;
  height: 20px;
  background: #3b82f6;
  border-radius: 2px;
  animation: squarePulse 1.2s ease-in-out infinite;
}

.square-spinner .square:nth-child(1) { animation-delay: 0s; }
.square-spinner .square:nth-child(2) { animation-delay: 0.1s; }
.square-spinner .square:nth-child(3) { animation-delay: 0.2s; }
.square-spinner .square:nth-child(4) { animation-delay: 0.3s; }

@keyframes squarePulse {
  0%, 70%, 100% {
    transform: scale(1);
    opacity: 1;
  }
  35% {
    transform: scale(0.8);
    opacity: 0.7;
  }
}

/* Progress Bars */
.progress-item {
  margin-bottom: 2rem;
}

.progress-label {
  display: block;
  font-weight: 600;
  color: #374151;
  margin-bottom: 0.5rem;
  font-size: 0.875rem;
}

.progress-bar {
  position: relative;
  width: 100%;
  height: 12px;
  background: #e5e7eb;
  border-radius: 6px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: #3b82f6;
  border-radius: 6px;
  transition: width 0.3s ease;
  position: relative;
}

.progress-text {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  font-size: 0.75rem;
  font-weight: 600;
  color: #374151;
}

/* Gradient Progress */
.gradient-progress .progress-fill {
  background: linear-gradient(90deg, #3b82f6, #1d4ed8, #7c3aed);
}

/* Animated Progress */
.animated-progress .progress-fill {
  overflow: hidden;
}

.progress-shine {
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.4),
    transparent
  );
  animation: shine 2s ease-in-out infinite;
}

@keyframes shine {
  0% { left: -100%; }
  100% { left: 100%; }
}

/* Step Progress */
.step-progress {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 0;
}

.step {
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  z-index: 2;
}

.step-circle {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #e5e7eb;
  color: #9ca3af;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 0.875rem;
  transition: all 0.3s ease;
  margin-bottom: 0.5rem;
}

.step.completed .step-circle {
  background: #10b981;
  color: white;
}

.step.active .step-circle {
  background: #3b82f6;
  color: white;
  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2);
}

.step-circle svg {
  width: 20px;
  height: 20px;
}

.step-label {
  font-size: 0.75rem;
  color: #6b7280;
  font-weight: 500;
  text-align: center;
}

.step.completed .step-label,
.step.active .step-label {
  color: #374151;
  font-weight: 600;
}

.step-connector {
  flex: 1;
  height: 2px;
  background: #e5e7eb;
  margin: 0 1rem;
  position: relative;
  z-index: 1;
}

.step-connector.completed {
  background: #10b981;
}

.step-connector.active {
  background: linear-gradient(90deg, #10b981 50%, #e5e7eb 50%);
}

/* Circular Progress */
.circular-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 2rem;
  justify-items: center;
}

.circular-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.circular-progress {
  position: relative;
  width: 120px;
  height: 120px;
}

.circular-svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.circular-bg {
  fill: none;
  stroke: #e5e7eb;
  stroke-width: 8;
}

.circular-fill {
  fill: none;
  stroke: #3b82f6;
  stroke-width: 8;
  stroke-linecap: round;
  stroke-dasharray: 283;
  stroke-dashoffset: 283;
  transition: stroke-dashoffset 0.3s ease;
}

.circular-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.25rem;
  font-weight: 700;
  color: #374151;
}

.circular-label {
  font-size: 0.875rem;
  color: #6b7280;
  font-weight: 500;
}

/* Multi-layer Circular */
.multi-layer .circular-fill.primary {
  stroke: #3b82f6;
  stroke-width: 6;
}

.multi-layer .circular-fill.secondary {
  stroke: #10b981;
  stroke-width: 4;
  stroke-dasharray: 188;
  stroke-dashoffset: 188;
}

.multi-layer .circular-bg.secondary {
  stroke: #f3f4f6;
  stroke-width: 4;
}

/* Loading States */
.loading-states {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
  flex-wrap: wrap;
}

.btn-loading {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
  min-width: 120px;
  background: #3b82f6;
  color: white;
}

.btn-loading:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}

.btn-loading.success {
  background: #10b981;
}

.btn-loading.error {
  background: #ef4444;
}

.btn-text,
.btn-spinner,
.btn-success,
.btn-error {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  transition: all 0.3s ease;
}

.btn-spinner,
.btn-success,
.btn-error {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  opacity: 0;
  transform: translateY(100%);
}

.btn-loading.loading .btn-text {
  opacity: 0;
  transform: translateY(-100%);
}

.btn-loading.loading .btn-spinner {
  opacity: 1;
  transform: translateY(0);
}

.btn-loading.success .btn-text,
.btn-loading.success .btn-spinner {
  opacity: 0;
  transform: translateY(-100%);
}

.btn-loading.success .btn-success {
  opacity: 1;
  transform: translateY(0);
}

.btn-loading.error .btn-text,
.btn-loading.error .btn-spinner {
  opacity: 0;
  transform: translateY(-100%);
}

.btn-loading.error .btn-error {
  opacity: 1;
  transform: translateY(0);
}

.mini-spinner {
  width: 20px;
  height: 20px;
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-top: 2px solid white;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

.btn-success svg,
.btn-error svg {
  width: 20px;
  height: 20px;
}

/* Card Loading State */
.loading-card {
  max-width: 400px;
  margin: 0 auto;
}

.card-skeleton {
  padding: 1.5rem;
  background: white;
  border-radius: 12px;
  border: 1px solid #e5e7eb;
  display: flex;
  gap: 1rem;
}

.skeleton-avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%);
  background-size: 200% 100%;
  animation: shimmer 2s ease-in-out infinite;
  flex-shrink: 0;
}

.skeleton-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.skeleton-line {
  height: 16px;
  border-radius: 8px;
  background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%);
  background-size: 200% 100%;
  animation: shimmer 2s ease-in-out infinite;
}

.skeleton-line.long { width: 100%; }
.skeleton-line.medium { width: 75%; }
.skeleton-line.short { width: 50%; }

@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

/* Interactive Demo */
.demo-controls {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
  flex-wrap: wrap;
  justify-content: center;
}

.demo-btn {
  padding: 0.5rem 1rem;
  border: 2px solid #3b82f6;
  background: white;
  color: #3b82f6;
  border-radius: 6px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
}

.demo-btn:hover {
  background: #3b82f6;
  color: white;
}

.demo-progress {
  max-width: 500px;
  margin: 0 auto;
}

.demo-bar {
  margin-bottom: 2rem;
}

.demo-spinner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.demo-status {
  font-weight: 600;
  color: #374151;
}

/* Dark Theme */
.dark .animation-section {
  background: #1f2937;
  border-color: #374151;
}

.dark .animation-section h3 {
  color: white;
}

.dark .spinner-label,
.dark .circular-label {
  color: #d1d5db;
}

.dark .progress-bar {
  background: #374151;
}

.dark .progress-text {
  color: #e5e7eb;
}

.dark .step-circle {
  background: #374151;
  color: #d1d5db;
}

.dark .step-connector {
  background: #374151;
}

.dark .circular-bg {
  stroke: #374151;
}

.dark .circular-text {
  color: #e5e7eb;
}

.dark .card-skeleton {
  background: #1f2937;
  border-color: #374151;
}

.dark .skeleton-avatar,
.dark .skeleton-line {
  background: linear-gradient(90deg, #374151 25%, #4b5563 50%, #374151 75%);
  background-size: 200% 100%;
}

/* Responsive Design */
@media (max-width: 768px) {
  .loading-container {
    padding: 1rem;
  }
  
  .animation-section {
    padding: 1.5rem;
  }
  
  .spinner-grid {
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
    gap: 1.5rem;
  }
  
  .circular-grid {
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  }
  
  .step-progress {
    flex-direction: column;
    gap: 1rem;
  }
  
  .step-connector {
    width: 2px;
    height: 30px;
    margin: 0;
  }
  
  .loading-states {
    flex-direction: column;
    align-items: center;
  }
  
  .demo-controls {
    flex-direction: column;
    align-items: center;
  }
}

@media (max-width: 480px) {
  .spinner {
    width: 50px;
    height: 50px;
  }
  
  .circular-progress {
    width: 100px;
    height: 100px;
  }
  
  .btn-loading {
    min-width: 100px;
    padding: 0.625rem 1.25rem;
  }
}

/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
  .spinner-circle,
  .dot,
  .pulse-ring,
  .wave-bar,
  .orbit-path,
  .square,
  .progress-shine,
  .mini-spinner,
  .skeleton-avatar,
  .skeleton-line {
    animation: none;
  }
  
  .circular-fill {
    transition: none;
  }
}
class LoadingAnimations {
  constructor() {
    this.progressBars = document.querySelectorAll('.progress-bar[data-progress]');
    this.circularProgress = document.querySelectorAll('.circular-progress[data-progress]');
    this.demoControls = document.querySelector('.demo-controls');
    this.demoBar = document.querySelector('.demo-bar');
    this.demoSpinner = document.querySelector('.demo-spinner');
    this.demoStatus = document.querySelector('.demo-status');
    
    this.init();
  }

  init() {
    this.initializeProgressBars();
    this.initializeCircularProgress();
    this.setupDemoControls();
    this.setupButtonStates();
    this.observeElements();
  }

  initializeProgressBars() {
    this.progressBars.forEach(bar => {
      const progress = parseInt(bar.dataset.progress);
      const fill = bar.querySelector('.progress-fill');
      const text = bar.querySelector('.progress-text');
      
      if (fill && text) {
        // Animate on load
        setTimeout(() => {
          fill.style.width = `${progress}%`;
          this.animateNumber(text, 0, progress, 1000);
        }, 100);
      }
    });
  }

  initializeCircularProgress() {
    this.circularProgress.forEach(circle => {
      const progress = parseInt(circle.dataset.progress);
      const fill = circle.querySelector('.circular-fill');
      const text = circle.querySelector('.circular-text');
      
      if (fill && text) {
        const circumference = 2 * Math.PI * 45; // radius = 45
        const offset = circumference - (progress / 100) * circumference;
        
        fill.style.strokeDasharray = circumference;
        fill.style.strokeDashoffset = circumference;
        
        // Animate on load
        setTimeout(() => {
          fill.style.strokeDashoffset = offset;
          this.animateNumber(text, 0, progress, 1000, '%');
        }, 100);
        
        // Handle multi-layer
        if (circle.classList.contains('multi-layer')) {
          const secondaryFill = circle.querySelector('.circular-fill.secondary');
          if (secondaryFill) {
            const secondaryCircumference = 2 * Math.PI * 30; // radius = 30
            const secondaryProgress = Math.min(progress + 10, 100);
            const secondaryOffset = secondaryCircumference - (secondaryProgress / 100) * secondaryCircumference;
            
            secondaryFill.style.strokeDasharray = secondaryCircumference;
            secondaryFill.style.strokeDashoffset = secondaryCircumference;
            
            setTimeout(() => {
              secondaryFill.style.strokeDashoffset = secondaryOffset;
            }, 200);
          }
        }
      }
    });
  }

  animateNumber(element, start, end, duration, suffix = '%') {
    const startTime = performance.now();
    
    const animate = (currentTime) => {
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);
      
      const current = Math.floor(start + (end - start) * this.easeOutCubic(progress));
      element.textContent = current + suffix;
      
      if (progress < 1) {
        requestAnimationFrame(animate);
      }
    };
    
    requestAnimationFrame(animate);
  }

  easeOutCubic(t) {
    return 1 - Math.pow(1 - t, 3);
  }

  setupDemoControls() {
    if (!this.demoControls) return;
    
    const buttons = this.demoControls.querySelectorAll('.demo-btn');
    let currentProgress = 0;
    let progressInterval;
    
    buttons.forEach(button => {
      button.addEventListener('click', () => {
        const action = button.dataset.action;
        
        switch (action) {
          case 'start':
            this.startDemo();
            break;
          case 'progress':
            this.simulateProgress();
            break;
          case 'complete':
            this.completeDemo();
            break;
          case 'reset':
            this.resetDemo();
            break;
        }
      });
    });
  }

  startDemo() {
    this.demoSpinner.style.display = 'flex';
    this.demoStatus.textContent = 'Initializing...';
    
    setTimeout(() => {
      this.demoStatus.textContent = 'Loading...';
    }, 1000);
  }

  simulateProgress() {
    this.demoSpinner.style.display = 'none';
    
    let progress = 0;
    const interval = setInterval(() => {
      progress += Math.random() * 15;
      if (progress >= 100) {
        progress = 100;
        clearInterval(interval);
        this.completeDemo();
      }
      
      this.updateDemoProgress(progress);
    }, 300);
  }

  updateDemoProgress(progress) {
    const fill = this.demoBar.querySelector('.progress-fill');
    const text = this.demoBar.querySelector('.progress-text');
    
    fill.style.width = `${progress}%`;
    text.textContent = `${Math.floor(progress)}%`;
    
    this.demoBar.dataset.progress = Math.floor(progress);
  }

  completeDemo() {
    this.updateDemoProgress(100);
    this.demoSpinner.style.display = 'none';
    
    // Show success state
    setTimeout(() => {
      const fill = this.demoBar.querySelector('.progress-fill');
      fill.style.background = '#10b981';
    }, 500);
  }

  resetDemo() {
    this.updateDemoProgress(0);
    this.demoSpinner.style.display = 'none';
    
    const fill = this.demoBar.querySelector('.progress-fill');
    fill.style.background = '#3b82f6';
  }

  setupButtonStates() {
    const loadingButtons = document.querySelectorAll('#loadingBtn1, #loadingBtn2, #loadingBtn3');
    
    loadingButtons.forEach((button, index) => {
      button.addEventListener('click', () => {
        if (index === 0) {
          this.simulateButtonLoading(button);
        }
      });
    });
  }

  simulateButtonLoading(button) {
    button.classList.add('loading');
    button.disabled = true;
    
    setTimeout(() => {
      button.classList.remove('loading');
      button.classList.add('success');
      
      setTimeout(() => {
        button.classList.remove('success');
        button.disabled = false;
      }, 2000);
    }, 2000);
  }

  observeElements() {
    if ('IntersectionObserver' in window) {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            this.animateElement(entry.target);
          }
        });
      }, {
        threshold: 0.1,
        rootMargin: '50px'
      });
      
      // Observe all animated elements
      const animatedElements = document.querySelectorAll(
        '.progress-bar, .circular-progress, .step-progress'
      );
      
      animatedElements.forEach(el => observer.observe(el));
    }
  }

  animateElement(element) {
    if (element.classList.contains('progress-bar')) {
      const progress = parseInt(element.dataset.progress);
      const fill = element.querySelector('.progress-fill');
      const text = element.querySelector('.progress-text');
      
      if (fill && text && !element.classList.contains('animated')) {
        element.classList.add('animated');
        fill.style.width = '0%';
        
        setTimeout(() => {
          fill.style.width = `${progress}%`;
          this.animateNumber(text, 0, progress, 1000);
        }, 100);
      }
    }
    
    if (element.classList.contains('circular-progress')) {
      const progress = parseInt(element.dataset.progress);
      const fill = element.querySelector('.circular-fill');
      const text = element.querySelector('.circular-text');
      
      if (fill && text && !element.classList.contains('animated')) {
        element.classList.add('animated');
        const circumference = 2 * Math.PI * 45;
        const offset = circumference - (progress / 100) * circumference;
        
        fill.style.strokeDashoffset = circumference;
        
        setTimeout(() => {
          fill.style.strokeDashoffset = offset;
          this.animateNumber(text, 0, progress, 1000, '%');
        }, 100);
      }
    }
  }

  // Public API
  setProgress(elementId, progress) {
    const element = document.getElementById(elementId);
    if (!element) return;
    
    element.dataset.progress = progress;
    
    if (element.classList.contains('progress-bar')) {
      const fill = element.querySelector('.progress-fill');
      const text = element.querySelector('.progress-text');
      
      if (fill && text) {
        fill.style.width = `${progress}%`;
        text.textContent = `${progress}%`;
      }
    }
    
    if (element.classList.contains('circular-progress')) {
      const fill = element.querySelector('.circular-fill');
      const text = element.querySelector('.circular-text');
      
      if (fill && text) {
        const circumference = 2 * Math.PI * 45;
        const offset = circumference - (progress / 100) * circumference;
        
        fill.style.strokeDashoffset = offset;
        text.textContent = `${progress}%`;
      }
    }
  }

  showLoading(elementId) {
    const element = document.getElementById(elementId);
    if (element && element.classList.contains('btn-loading')) {
      element.classList.add('loading');
      element.disabled = true;
    }
  }

  hideLoading(elementId, state = 'default') {
    const element = document.getElementById(elementId);
    if (element && element.classList.contains('btn-loading')) {
      element.classList.remove('loading');
      
      if (state === 'success') {
        element.classList.add('success');
        setTimeout(() => {
          element.classList.remove('success');
          element.disabled = false;
        }, 2000);
      } else if (state === 'error') {
        element.classList.add('error');
        setTimeout(() => {
          element.classList.remove('error');
          element.disabled = false;
        }, 2000);
      } else {
        element.disabled = false;
      }
    }
  }

  createSpinner(type = 'classic', size = 'medium') {
    const spinner = document.createElement('div');
    spinner.className = `spinner ${type}-spinner ${size}`;
    
    switch (type) {
      case 'classic':
        spinner.innerHTML = '<div class="spinner-circle"></div>';
        break;
      case 'dots':
        spinner.innerHTML = '<div class="dot"></div><div class="dot"></div><div class="dot"></div>';
        break;
      case 'pulse':
        spinner.innerHTML = '<div class="pulse-ring"></div><div class="pulse-ring"></div><div class="pulse-ring"></div>';
        break;
      default:
        spinner.innerHTML = '<div class="spinner-circle"></div>';
    }
    
    return spinner;
  }
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
  new LoadingAnimations();
});

HTML

4

lines

CSS

7

lines


                <div class="loading-container">
  <h2>Loading Animations & Progress Indicators</h2>
  <p>Modern loading animations and progress indicators</p>
</div>

              
4lines
150characters
HTMLLanguage

Related Code Snippets

Explore template packs

Need larger building blocks? Browse responsive landing pages and component bundles.

Open HTML Template Library →