Back

Cómo Crear una Barra de Progreso de Carga con JavaScript

Cómo Crear una Barra de Progreso de Carga con JavaScript

Cargar archivos sin retroalimentación visual deja a los usuarios preguntándose si algo está sucediendo. Una barra de progreso transforma esta incertidumbre en una experiencia clara y accesible que muestra exactamente cuánto de la carga se ha completado.

Este artículo demuestra cómo construir una barra de progreso de carga en tiempo real utilizando la API XMLHttpRequest de JavaScript, elementos HTML semánticos y mejores prácticas de accesibilidad, creando una solución que funciona de manera confiable en todos los navegadores modernos.

Puntos Clave

  • XMLHttpRequest sigue siendo la API estándar para rastrear el progreso de carga, ya que la API Fetch no admite eventos de progreso de carga
  • HTML semántico con atributos ARIA garantiza accesibilidad para todos los usuarios
  • La solución funciona en todos los navegadores modernos sin dependencias externas
  • El manejo adecuado de errores y los controles de usuario crean una experiencia de carga robusta

Configuración de la Estructura HTML

Comienza con HTML semántico que proporcione retroalimentación tanto visual como accesible:

<form id="uploadForm">
  <label for="fileInput">Select file to upload:</label>
  <input type="file" id="fileInput" name="file" accept="image/*,application/pdf">
  
  <progress id="uploadProgress" value="0" max="100" aria-label="Upload progress"></progress>
  <span id="progressText" aria-live="polite">0% uploaded</span>
  
  <button type="submit">Upload File</button>
  <button type="button" id="cancelBtn" disabled>Cancel Upload</button>
</form>

El elemento <progress> proporciona semántica nativa que las tecnologías de asistencia comprenden. El porcentaje de texto que lo acompaña asegura que los usuarios no dependan únicamente de señales visuales. El atributo aria-live="polite" anuncia los cambios de porcentaje a los lectores de pantalla sin interrumpir otro contenido.

Por Qué XMLHttpRequest para el Progreso de Carga

Aunque la API Fetch maneja la mayoría de las solicitudes HTTP modernas de manera elegante, aún no expone eventos de progreso de carga. El objeto XMLHttpRequest sigue siendo la herramienta correcta para rastrear implementaciones de progreso de carga porque proporciona el manejador de eventos xhr.upload.onprogress.

Ten en cuenta que solo XMLHttpRequest síncrono está obsoleto; XHR asíncrono sigue siendo una API base bien soportada que es perfecta para este caso de uso.

Implementación de la Barra de Progreso de Carga con JavaScript

Aquí está la implementación completa que maneja la selección de archivos, el seguimiento de carga y los controles de usuario:

const form = document.getElementById('uploadForm');
const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('uploadProgress');
const progressText = document.getElementById('progressText');
const cancelBtn = document.getElementById('cancelBtn');

let currentXHR = null;

form.addEventListener('submit', (e) => {
  e.preventDefault();
  
  const file = fileInput.files[0];
  if (!file) return;
  
  // Validate file size (10MB limit example)
  const maxSize = 10 * 1024 * 1024;
  if (file.size > maxSize) {
    alert('File size exceeds 10MB limit');
    return;
  }
  
  uploadFile(file);
});

function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);
  
  currentXHR = new XMLHttpRequest();
  
  // Track upload progress
  currentXHR.upload.onprogress = (event) => {
    if (event.lengthComputable) {
      const percentComplete = Math.round((event.loaded / event.total) * 100);
      updateProgress(percentComplete);
    } else {
      // Handle indeterminate progress
      progressBar.removeAttribute('value');
      progressText.textContent = 'Uploading...';
    }
  };
  
  // Handle completion
  currentXHR.onload = function() {
    if (currentXHR.status === 200) {
      updateProgress(100);
      progressText.textContent = 'Upload complete!';
      resetForm();
    } else {
      handleError('Upload failed: ' + currentXHR.statusText);
    }
  };
  
  // Handle errors
  currentXHR.onerror = () => handleError('Network error occurred');
  currentXHR.onabort = () => handleError('Upload cancelled');
  
  // Send request
  currentXHR.open('POST', '/api/upload', true);
  currentXHR.send(formData);
  
  // Enable cancel button
  cancelBtn.disabled = false;
}

function updateProgress(percent) {
  progressBar.value = percent;
  progressText.textContent = `${percent}% uploaded`;
}

function handleError(message) {
  progressText.textContent = message;
  progressBar.value = 0;
  resetForm();
}

function resetForm() {
  cancelBtn.disabled = true;
  currentXHR = null;
  setTimeout(() => {
    progressBar.value = 0;
    progressText.textContent = '0% uploaded';
  }, 2000);
}

// Cancel upload functionality
cancelBtn.addEventListener('click', () => {
  if (currentXHR) {
    currentXHR.abort();
  }
});

Comprendiendo el Evento de Progreso

El evento xhr.upload.onprogress proporciona tres propiedades cruciales:

  • event.loaded: Bytes ya cargados
  • event.total: Tamaño total del archivo en bytes
  • event.lengthComputable: Booleano que indica si se conoce el tamaño total

Cuando lengthComputable es verdadero, calcula el porcentaje como (loaded / total) * 100. Cuando es falso, el servidor no ha proporcionado encabezados Content-Length, así que muestra un estado de progreso indeterminado eliminando el atributo value del elemento progress.

Estilización para una Mejor Experiencia de Usuario

Agrega CSS para hacer que la implementación del progreso de carga sea visualmente clara:

progress {
  width: 100%;
  height: 24px;
  margin: 10px 0;
}

/* Webkit browsers */
progress::-webkit-progress-bar {
  background-color: #f0f0f0;
  border-radius: 4px;
}

progress::-webkit-progress-value {
  background-color: #4CAF50;
  border-radius: 4px;
  transition: width 0.3s ease;
}

/* Firefox */
progress::-moz-progress-bar {
  background-color: #4CAF50;
  border-radius: 4px;
}

#progressText {
  display: block;
  margin-top: 5px;
  font-weight: 600;
}

Consideraciones del Lado del Servidor

El endpoint del servidor debe aceptar cargas multipart/form-data. Aquí hay un ejemplo mínimo de Node.js usando Express y Multer:

const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

app.post('/api/upload', upload.single('file'), (req, res) => {
  // File is available as req.file
  res.json({ success: true, filename: req.file.filename });
});

Para producción, agrega validación de tipo de archivo, escaneo de virus y manejo adecuado de almacenamiento (sistema de archivos local o servicios en la nube como AWS S3).

Compatibilidad del Navegador y Mejora Progresiva

Este enfoque funciona en todos los navegadores modernos porque XMLHttpRequest Level 2 (que incluye progreso de carga) ha sido compatible desde:

  • Chrome 7+
  • Firefox 3.5+
  • Safari 5+
  • Edge (todas las versiones)

Para navegadores más antiguos, la carga aún funciona; los usuarios simplemente no verán actualizaciones de progreso. El formulario se degrada elegantemente a una carga de archivo estándar.

Conclusión

Construir una barra de progreso de carga accesible requiere XMLHttpRequest para eventos de progreso, HTML semántico para la estructura y JavaScript cuidadoso para manejar varios estados de carga. Esta implementación proporciona retroalimentación en tiempo real que funciona para todos los usuarios, incluidos aquellos que usan tecnologías de asistencia, mientras mantiene amplia compatibilidad con navegadores sin requerir bibliotecas externas.

Preguntas Frecuentes

La API Fetch no admite eventos de progreso de carga. XMLHttpRequest proporciona el manejador de eventos xhr.upload.onprogress específicamente para rastrear el progreso de carga, lo que lo convierte en la única opción viable para barras de progreso en tiempo real.

Crea instancias separadas de XMLHttpRequest para cada archivo o ponlos en cola secuencialmente. Rastrea el progreso individual y calcula el progreso general promediando porcentajes o sumando bytes cargados en todos los archivos.

Sin encabezados Content-Length, event.lengthComputable devuelve falso. Muestra un estado de progreso indeterminado eliminando el atributo value del elemento progress y mostrando texto de carga genérico en lugar de porcentajes.

XMLHttpRequest estándar no admite cargas reanudables. Para esta funcionalidad, implementa cargas fragmentadas con soporte del lado del servidor o usa bibliotecas especializadas que manejen la división de archivos y la lógica de reanudación.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay