Back

Como Criar uma Barra de Progresso de Upload com JavaScript

Como Criar uma Barra de Progresso de Upload com JavaScript

Fazer upload de arquivos sem feedback visual deixa os usuários sem saber se algo está acontecendo. Uma barra de progresso transforma essa incerteza em uma experiência clara e acessível que mostra exatamente quanto do upload está completo.

Este artigo demonstra como criar uma barra de progresso de upload em tempo real usando a API XMLHttpRequest do JavaScript, elementos HTML semânticos e práticas recomendadas de acessibilidade—criando uma solução que funciona de forma confiável em todos os navegadores modernos.

Pontos-Chave

  • XMLHttpRequest continua sendo a API padrão para rastrear o progresso de upload, já que a Fetch API não suporta eventos de progresso de upload
  • HTML semântico com atributos ARIA garante acessibilidade para todos os usuários
  • A solução funciona em todos os navegadores modernos sem dependências externas
  • Tratamento adequado de erros e controles do usuário criam uma experiência de upload robusta

Configurando a Estrutura HTML

Comece com HTML semântico que fornece feedback tanto visual quanto acessível:

<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>

O elemento <progress> fornece semântica nativa que as tecnologias assistivas compreendem. A porcentagem de texto que o acompanha garante que os usuários não dependam apenas de pistas visuais. O atributo aria-live="polite" anuncia mudanças de porcentagem para leitores de tela sem interromper outro conteúdo.

Por Que XMLHttpRequest para Progresso de Upload

Embora a Fetch API lide com a maioria das requisições HTTP modernas de forma elegante, ela ainda não expõe eventos de progresso de upload. O objeto XMLHttpRequest continua sendo a ferramenta correta para implementações de rastreamento de progresso de upload porque fornece o manipulador de eventos xhr.upload.onprogress.

Note que apenas o XMLHttpRequest síncrono está obsoleto—o XHR assíncrono continua sendo uma API básica, bem suportada e perfeita para este caso de uso.

Implementando a Barra de Progresso de Upload com JavaScript

Aqui está a implementação completa que lida com seleção de arquivo, rastreamento de upload e controles do usuário:

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();
  }
});

Entendendo o Evento de Progresso

O evento xhr.upload.onprogress fornece três propriedades cruciais:

  • event.loaded: Bytes já enviados
  • event.total: Tamanho total do arquivo em bytes
  • event.lengthComputable: Booleano indicando se o tamanho total é conhecido

Quando lengthComputable é verdadeiro, calcule a porcentagem como (loaded / total) * 100. Quando falso, o servidor não forneceu cabeçalhos Content-Length, então mostre um estado de progresso indeterminado removendo o atributo value do elemento progress.

Estilização para Melhor Experiência do Usuário

Adicione CSS para tornar a implementação do progresso de upload 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;
}

Considerações do Lado do Servidor

O endpoint do servidor deve aceitar uploads multipart/form-data. Aqui está um exemplo mínimo em Node.js usando Express e 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 produção, adicione validação de tipo de arquivo, verificação de vírus e tratamento adequado de armazenamento (sistema de arquivos local ou serviços em nuvem como AWS S3).

Compatibilidade com Navegadores e Aprimoramento Progressivo

Esta abordagem funciona em todos os navegadores modernos porque XMLHttpRequest Level 2 (que inclui progresso de upload) tem sido suportado desde:

  • Chrome 7+
  • Firefox 3.5+
  • Safari 5+
  • Edge (todas as versões)

Para navegadores mais antigos, o upload ainda funciona—os usuários apenas não verão atualizações de progresso. O formulário degrada graciosamente para um upload de arquivo padrão.

Conclusão

Construir uma barra de progresso de upload acessível requer XMLHttpRequest para eventos de progresso, HTML semântico para estrutura e JavaScript cuidadoso para lidar com vários estados de upload. Esta implementação fornece feedback em tempo real que funciona para todos os usuários, incluindo aqueles que usam tecnologias assistivas, mantendo ampla compatibilidade com navegadores sem exigir bibliotecas externas.

Perguntas Frequentes

A Fetch API não suporta eventos de progresso de upload. XMLHttpRequest fornece o manipulador de eventos xhr.upload.onprogress especificamente para rastrear o progresso de upload, tornando-o a única opção viável para barras de progresso em tempo real.

Crie instâncias XMLHttpRequest separadas para cada arquivo ou enfileire-os sequencialmente. Rastreie o progresso individual e calcule o progresso geral fazendo a média das porcentagens ou somando os bytes carregados em todos os arquivos.

Sem cabeçalhos Content-Length, event.lengthComputable retorna falso. Mostre um estado de progresso indeterminado removendo o atributo value do elemento progress e exibindo texto de carregamento genérico em vez de porcentagens.

O XMLHttpRequest padrão não suporta uploads retomáveis. Para esta funcionalidade, implemente uploads fragmentados com suporte do lado do servidor ou use bibliotecas especializadas que lidam com lógica de divisão de arquivos e retomada.

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