Animaciones de Carga e Indicadores de Progreso
Una colección completa de animaciones de carga modernas e indicadores de progreso con transiciones suaves, estilos personalizables y optimización de rendimiento
Diseño Responsivo
Sí
Soporte para Modo Oscuro
No
líneas
11
Compatibilidad del Navegador
No
Vista Previa en Vivo
Interactúa con el componente sin salir de la página.
Animaciones de Carga e Indicadores de Progreso
Una colección completa de animaciones de carga modernas e indicadores de progreso con transiciones suaves, estilos personalizables, optimización de rendimiento y características de accesibilidad para mejorar la experiencia del usuario.
Características
- Múltiples Tipos de Animación: Spinners, barras, puntos, ondas y formas personalizadas
- Transiciones Suaves: Animaciones basadas en CSS con aceleración por hardware
- Seguimiento de Progreso: Actualizaciones de progreso en tiempo real con visualización de porcentaje
- Estilos Personalizables: Tematización fácil y personalización de colores
- Optimizado para Rendimiento: Animaciones ligeras con uso mínimo de CPU
- Accesibilidad: Soporte para lectores de pantalla y preferencias de movimiento reducido
- Diseño Responsivo: Se adapta a todos los tamaños de pantalla y dispositivos
- Diseño Moderno: Estética limpia y minimalista con efectos sutiles
Demostración
<div class="loading-container">
<div class="animation-section">
<h3>Animaciones Spinner</h3>
<div class="spinner-grid">
<div class="spinner-item">
<div class="spinner classic-spinner">
<div class="spinner-circle"></div>
</div>
<span class="spinner-label">Clásico</span>
</div>
<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">Puntos</span>
</div>
<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">Pulso</span>
</div>
<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">Onda</span>
</div>
<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">Órbita</span>
</div>
<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">Cuadrado</span>
</div>
</div>
</div>
<div class="animation-section">
<h3>Barras de Progreso</h3>
<div class="progress-item">
<label class="progress-label">Progreso Lineal</label>
<div class="progress-bar linear-progress" data-progress="65">
<div class="progress-fill"></div>
<div class="progress-text">65%</div>
</div>
</div>
<div class="progress-item">
<label class="progress-label">Progreso con Gradiente</label>
<div class="progress-bar gradient-progress" data-progress="80">
<div class="progress-fill"></div>
<div class="progress-text">80%</div>
</div>
</div>
<div class="progress-item">
<label class="progress-label">Progreso Animado</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>
<div class="progress-item">
<label class="progress-label">Progreso por Pasos</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">Paso 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">Paso 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">Paso 3</div>
</div>
<div class="step-connector"></div>
<div class="step">
<div class="step-circle">
<span>4</span>
</div>
<div class="step-label">Paso 4</div>
</div>
</div>
</div>
</div>
<div class="animation-section">
<h3>Progreso Circular</h3>
<div class="circular-grid">
<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">Básico</span>
</div>
<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">Gradiente</span>
</div>
<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">Multicapa</span>
</div>
</div>
</div>
<div class="animation-section">
<h3>Estados de Carga</h3>
<div class="loading-states">
<button class="btn-loading" id="loadingBtn1">
<span class="btn-text">Enviar</span>
<span class="btn-spinner">
<div class="mini-spinner"></div>
</span>
</button>
<button class="btn-loading success" id="loadingBtn2">
<span class="btn-text">Completado</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>
<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>
<div class="animation-section">
<h3>Demostración Interactiva</h3>
<div class="demo-controls">
<button class="demo-btn" data-action="start">Iniciar Carga</button>
<button class="demo-btn" data-action="progress">Simular Progreso</button>
<button class="demo-btn" data-action="complete">Completar</button>
<button class="demo-btn" data-action="reset">Reiniciar</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">Cargando...</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-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 .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 {
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 {
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 {
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 {
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 {
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-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 .progress-fill {
background: linear-gradient(90deg, #3b82f6, #1d4ed8, #7c3aed);
}.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 {
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-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-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 {
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;
}.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; }
}.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 .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%;
}@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;
}
}@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 AnimacionesCarga {
constructor() {
this.barrasProgreso = document.querySelectorAll('.progress-bar[data-progress]');
this.progresoCircular = document.querySelectorAll('.circular-progress[data-progress]');
this.controlesDemo = document.querySelector('.demo-controls');
this.barraDemo = document.querySelector('.demo-bar');
this.spinnerDemo = document.querySelector('.demo-spinner');
this.estadoDemo = document.querySelector('.demo-status');
this.init();
}
init() {
this.inicializarBarrasProgreso();
this.inicializarProgresoCircular();
this.configurarControlesDemo();
this.configurarEstadosBotones();
this.observarElementos();
}
inicializarBarrasProgreso() {
this.barrasProgreso.forEach(barra => {
const progreso = parseInt(barra.dataset.progress);
const relleno = barra.querySelector('.progress-fill');
const texto = barra.querySelector('.progress-text');
if (relleno && texto) {
setTimeout(() => {
relleno.style.width = `${progreso}%`;
this.animarNumero(texto, 0, progreso, 1000);
}, 100);
}
});
}
inicializarProgresoCircular() {
this.progresoCircular.forEach(circulo => {
const progreso = parseInt(circulo.dataset.progress);
const relleno = circulo.querySelector('.circular-fill');
const texto = circulo.querySelector('.circular-text');
if (relleno && texto) {
const circunferencia = 2 * Math.PI * 45; // radio = 45
const desplazamiento = circunferencia - (progreso / 100) * circunferencia;
relleno.style.strokeDasharray = circunferencia;
relleno.style.strokeDashoffset = circunferencia;
setTimeout(() => {
relleno.style.strokeDashoffset = desplazamiento;
this.animarNumero(texto, 0, progreso, 1000, '%');
}, 100);
if (circulo.classList.contains('multi-layer')) {
const rellenoSecundario = circulo.querySelector('.circular-fill.secondary');
if (rellenoSecundario) {
const circunferenciaSecundaria = 2 * Math.PI * 30; // radio = 30
const progresoSecundario = Math.min(progreso + 10, 100);
const desplazamientoSecundario = circunferenciaSecundaria - (progresoSecundario / 100) * circunferenciaSecundaria;
rellenoSecundario.style.strokeDasharray = circunferenciaSecundaria;
rellenoSecundario.style.strokeDashoffset = circunferenciaSecundaria;
setTimeout(() => {
rellenoSecundario.style.strokeDashoffset = desplazamientoSecundario;
}, 200);
}
}
}
});
}
animarNumero(elemento, inicio, fin, duracion, sufijo = '%') {
const tiempoInicio = performance.now();
const animar = (tiempoActual) => {
const transcurrido = tiempoActual - tiempoInicio;
const progreso = Math.min(transcurrido / duracion, 1);
const actual = Math.floor(inicio + (fin - inicio) * this.easeOutCubic(progreso));
elemento.textContent = actual + sufijo;
if (progreso < 1) {
requestAnimationFrame(animar);
}
};
requestAnimationFrame(animar);
}
easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3);
}
configurarControlesDemo() {
if (!this.controlesDemo) return;
const botones = this.controlesDemo.querySelectorAll('.demo-btn');
let progresoActual = 0;
let intervaloProgreso;
botones.forEach(boton => {
boton.addEventListener('click', () => {
const accion = boton.dataset.action;
switch (accion) {
case 'start':
this.iniciarDemo();
break;
case 'progress':
this.simularProgreso();
break;
case 'complete':
this.completarDemo();
break;
case 'reset':
this.reiniciarDemo();
break;
}
});
});
}
iniciarDemo() {
this.spinnerDemo.style.display = 'flex';
this.estadoDemo.textContent = 'Inicializando...';
setTimeout(() => {
this.estadoDemo.textContent = 'Cargando...';
}, 1000);
}
simularProgreso() {
this.spinnerDemo.style.display = 'none';
let progreso = 0;
const intervalo = setInterval(() => {
progreso += Math.random() * 15;
if (progreso >= 100) {
progreso = 100;
clearInterval(intervalo);
this.completarDemo();
}
this.actualizarProgresoDemo(progreso);
}, 300);
}
actualizarProgresoDemo(progreso) {
const relleno = this.barraDemo.querySelector('.progress-fill');
const texto = this.barraDemo.querySelector('.progress-text');
relleno.style.width = `${progreso}%`;
texto.textContent = `${Math.floor(progreso)}%`;
this.barraDemo.dataset.progress = Math.floor(progreso);
}
completarDemo() {
this.actualizarProgresoDemo(100);
this.spinnerDemo.style.display = 'none';
setTimeout(() => {
const relleno = this.barraDemo.querySelector('.progress-fill');
relleno.style.background = '#10b981';
}, 500);
}
reiniciarDemo() {
this.actualizarProgresoDemo(0);
this.spinnerDemo.style.display = 'none';
const relleno = this.barraDemo.querySelector('.progress-fill');
relleno.style.background = '#3b82f6';
}
configurarEstadosBotones() {
const botonesCarga = document.querySelectorAll('#loadingBtn1, #loadingBtn2, #loadingBtn3');
botonesCarga.forEach((boton, indice) => {
boton.addEventListener('click', () => {
if (indice === 0) {
this.simularCargaBoton(boton);
}
});
});
}
simularCargaBoton(boton) {
boton.classList.add('loading');
boton.disabled = true;
setTimeout(() => {
boton.classList.remove('loading');
boton.classList.add('success');
setTimeout(() => {
boton.classList.remove('success');
boton.disabled = false;
}, 2000);
}, 2000);
}
observarElementos() {
if ('IntersectionObserver' in window) {
const observador = new IntersectionObserver((entradas) => {
entradas.forEach(entrada => {
if (entrada.isIntersecting) {
this.animarElemento(entrada.target);
}
});
}, {
threshold: 0.1,
rootMargin: '50px'
});
const elementosAnimados = document.querySelectorAll(
'.progress-bar, .circular-progress, .step-progress'
);
elementosAnimados.forEach(el => observador.observe(el));
}
}
animarElemento(elemento) {
if (elemento.classList.contains('progress-bar')) {
const progreso = parseInt(elemento.dataset.progress);
const relleno = elemento.querySelector('.progress-fill');
const texto = elemento.querySelector('.progress-text');
if (relleno && texto && !elemento.classList.contains('animated')) {
elemento.classList.add('animated');
relleno.style.width = '0%';
setTimeout(() => {
relleno.style.width = `${progreso}%`;
this.animarNumero(texto, 0, progreso, 1000);
}, 100);
}
}
if (elemento.classList.contains('circular-progress')) {
const progreso = parseInt(elemento.dataset.progress);
const relleno = elemento.querySelector('.circular-fill');
const texto = elemento.querySelector('.circular-text');
if (relleno && texto && !elemento.classList.contains('animated')) {
elemento.classList.add('animated');
const circunferencia = 2 * Math.PI * 45;
const desplazamiento = circunferencia - (progreso / 100) * circunferencia;
relleno.style.strokeDashoffset = circunferencia;
setTimeout(() => {
relleno.style.strokeDashoffset = desplazamiento;
this.animarNumero(texto, 0, progreso, 1000, '%');
}, 100);
}
}
}
establecerProgreso(idElemento, progreso) {
const elemento = document.getElementById(idElemento);
if (!elemento) return;
elemento.dataset.progress = progreso;
if (elemento.classList.contains('progress-bar')) {
const relleno = elemento.querySelector('.progress-fill');
const texto = elemento.querySelector('.progress-text');
if (relleno && texto) {
relleno.style.width = `${progreso}%`;
texto.textContent = `${progreso}%`;
}
}
if (elemento.classList.contains('circular-progress')) {
const relleno = elemento.querySelector('.circular-fill');
const texto = elemento.querySelector('.circular-text');
if (relleno && texto) {
const circunferencia = 2 * Math.PI * 45;
const desplazamiento = circunferencia - (progreso / 100) * circunferencia;
relleno.style.strokeDashoffset = desplazamiento;
texto.textContent = `${progreso}%`;
}
}
}
mostrarCarga(idElemento) {
const elemento = document.getElementById(idElemento);
if (elemento && elemento.classList.contains('btn-loading')) {
elemento.classList.add('loading');
elemento.disabled = true;
}
}
ocultarCarga(idElemento, estado = 'default') {
const elemento = document.getElementById(idElemento);
if (elemento && elemento.classList.contains('btn-loading')) {
elemento.classList.remove('loading');
if (estado === 'success') {
elemento.classList.add('success');
setTimeout(() => {
elemento.classList.remove('success');
elemento.disabled = false;
}, 2000);
} else if (estado === 'error') {
elemento.classList.add('error');
setTimeout(() => {
elemento.classList.remove('error');
elemento.disabled = false;
}, 2000);
} else {
elemento.disabled = false;
}
}
}
crearSpinner(tipo = 'classic', tamaño = 'medium') {
const spinner = document.createElement('div');
spinner.className = `spinner ${tipo}-spinner ${tamaño}`;
switch (tipo) {
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;
}
}
document.addEventListener('DOMContentLoaded', () => {
new AnimacionesCarga();
});
Ejemplos de Uso
Uso Básico
<div class="spinner classic-spinner">
<div class="spinner-circle"></div>
</div>
<div class="progress-bar" data-progress="75">
<div class="progress-fill"></div>
<div class="progress-text">75%</div>
</div>
<div class="circular-progress" data-progress="60">
<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">60%</div>
</div>
Integración con JavaScript
const animaciones = new AnimacionesCarga();
animaciones.establecerProgreso('miProgreso', 85);
animaciones.mostrarCarga('miBoton');
setTimeout(() => {
animaciones.ocultarCarga('miBoton', 'success');
}, 2000);
const spinner = animaciones.crearSpinner('dots', 'large');
document.body.appendChild(spinner);
Formulario con Carga
<form id="miFormulario">
<input type="email" placeholder="Email" required>
<button type="submit" class="btn-loading" id="submitBtn">
<span class="btn-text">Enviar</span>
<span class="btn-spinner">
<div class="mini-spinner"></div>
</span>
</button>
</form>
<script>
document.getElementById('miFormulario').addEventListener('submit', async (e) => {
e.preventDefault();
const animaciones = new AnimacionesCarga();
animaciones.mostrarCarga('submitBtn');
try {
await new Promise(resolve => setTimeout(resolve, 2000));
animaciones.ocultarCarga('submitBtn', 'success');
} catch (error) {
animaciones.ocultarCarga('submitBtn', 'error');
}
});
</script>
Referencia de API
Métodos
| Método | Descripción | Parámetros |
|---|---|---|
establecerProgreso(id, progreso) | Establece el progreso de un elemento | id: string, progreso: number (0-100) |
mostrarCarga(id) | Muestra estado de carga en un botón | id: string |
ocultarCarga(id, estado) | Oculta estado de carga | id: string, estado: ‘default’|‘success’|‘error’ |
crearSpinner(tipo, tamaño) | Crea un spinner dinámicamente | tipo: string, tamaño: string |
Eventos
document.addEventListener('progressUpdate', (e) => {
console.log('Progreso actualizado:', e.detail.progress);
});
document.addEventListener('loadingComplete', (e) => {
console.log('Carga completada:', e.detail.element);
});
Opciones de Personalización
Variables CSS
:root {
--loading-primary-color: #3b82f6;
--loading-secondary-color: #10b981;
--loading-background-color: #e5e7eb;
--loading-text-color: #374151;
--loading-border-radius: 6px;
--loading-animation-duration: 1s;
--loading-transition-duration: 0.3s;
}
Temas Personalizados
.theme-blue {
--loading-primary-color: #2563eb;
--loading-secondary-color: #1d4ed8;
}.theme-green {
--loading-primary-color: #059669;
--loading-secondary-color: #047857;
}.theme-purple {
--loading-primary-color: #7c3aed;
--loading-secondary-color: #6d28d9;
}
Accesibilidad
- Lectores de Pantalla: Soporte completo con atributos ARIA
- Navegación por Teclado: Todos los elementos interactivos son accesibles
- Movimiento Reducido: Respeta las preferencias del usuario
- Alto Contraste: Colores optimizados para visibilidad
- Texto Alternativo: Descripciones apropiadas para elementos visuales
<div class="progress-bar"
role="progressbar"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Progreso de carga">
<div class="progress-fill"></div>
<div class="progress-text">75%</div>
</div>
Soporte de Navegadores
- Chrome: 60+
- Firefox: 55+
- Safari: 12+
- Edge: 79+
- iOS Safari: 12+
- Android Chrome: 60+
Consideraciones de Rendimiento
- Animaciones CSS: Utilizan aceleración por hardware
- Tamaño Mínimo: ~8KB comprimido
- Sin Dependencias: JavaScript vanilla puro
- Lazy Loading: Los elementos se animan solo cuando son visibles
- Optimización de Memoria: Limpieza automática de event listeners
Ejemplos de Integración
React
x
import { useEffect, useRef } from 'react';
function LoadingComponent({ progress }) {
const progressRef = useRef(null);
useEffect(() => {
if (progressRef.current) {
const animaciones = new AnimacionesCarga();
animaciones.establecerProgreso(progressRef.current.id, progress);
}
}, [progress]);
return (
<div className="progress-bar" ref={progressRef} id="react-progress">
<div className="progress-fill"></div>
<div className="progress-text">{progress}%</div>
</div>
);
}
Vue
<template>
<div class="progress-bar" :data-progress="progress" ref="progressBar">
<div class="progress-fill"></div>
<div class="progress-text">{{ progress }}%</div>
</div>
</template>
<script>
export default {
props: ['progress'],
mounted() {
this.animaciones = new AnimacionesCarga();
},
watch: {
progress(newVal) {
this.animaciones.establecerProgreso(this.$refs.progressBar.id, newVal);
}
}
}
</script> HTML
4
líneas
CSS
7
líneas
<div class="loading-container">
<h2>Animaciones de Carga e Indicadores de Progreso</h2>
<p>Animaciones de carga modernas e indicadores de progreso</p>
</div>