Back

Как создать индикатор прогресса загрузки с помощью JavaScript

Как создать индикатор прогресса загрузки с помощью JavaScript

Загрузка файлов без визуальной обратной связи оставляет пользователей в неведении о том, происходит ли что-либо вообще. Индикатор прогресса превращает эту неопределённость в понятный и доступный интерфейс, который точно показывает, какая часть загрузки завершена.

В этой статье демонстрируется, как создать индикатор прогресса загрузки в реальном времени с использованием XMLHttpRequest API JavaScript, семантических HTML-элементов и лучших практик доступности — создавая решение, которое надёжно работает во всех современных браузерах.

Ключевые выводы

  • XMLHttpRequest остаётся стандартным API для отслеживания прогресса загрузки, поскольку Fetch API не поддерживает события прогресса загрузки
  • Семантический HTML с ARIA-атрибутами обеспечивает доступность для всех пользователей
  • Решение работает во всех современных браузерах без внешних зависимостей
  • Правильная обработка ошибок и элементы управления создают надёжный интерфейс загрузки

Настройка HTML-структуры

Начните с семантического HTML, который обеспечивает как визуальную, так и доступную обратную связь:

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

Элемент <progress> обеспечивает нативную семантику, понятную вспомогательным технологиям. Сопутствующий текстовый процент гарантирует, что пользователи не полагаются исключительно на визуальные подсказки. Атрибут aria-live="polite" объявляет изменения процента программам чтения с экрана, не прерывая другой контент.

Почему XMLHttpRequest для прогресса загрузки

Хотя Fetch API элегантно обрабатывает большинство современных HTTP-запросов, он всё ещё не предоставляет события прогресса загрузки. Объект XMLHttpRequest остаётся правильным инструментом для отслеживания прогресса загрузки, поскольку он предоставляет обработчик события xhr.upload.onprogress.

Обратите внимание, что устаревшим является только синхронный XMLHttpRequest — асинхронный XHR остаётся базовым, хорошо поддерживаемым API, который идеально подходит для этого случая использования.

Реализация индикатора прогресса загрузки с помощью JavaScript

Вот полная реализация, которая обрабатывает выбор файла, отслеживание загрузки и элементы управления:

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

Понимание события прогресса

Событие xhr.upload.onprogress предоставляет три ключевых свойства:

  • event.loaded: уже загруженные байты
  • event.total: общий размер файла в байтах
  • event.lengthComputable: булево значение, указывающее, известен ли общий размер

Когда lengthComputable равно true, вычислите процент как (loaded / total) * 100. Когда false, сервер не предоставил заголовки Content-Length, поэтому покажите неопределённое состояние прогресса, удалив атрибут value элемента progress.

Стилизация для улучшения пользовательского опыта

Добавьте CSS, чтобы сделать прогресс загрузки визуально понятным:

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

Серверные соображения

Серверная конечная точка должна принимать загрузки multipart/form-data. Вот минимальный пример на Node.js с использованием Express и 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 });
});

Для продакшена добавьте валидацию типов файлов, проверку на вирусы и правильную обработку хранения (локальная файловая система или облачные сервисы, такие как AWS S3).

Совместимость с браузерами и прогрессивное улучшение

Этот подход работает во всех современных браузерах, поскольку XMLHttpRequest Level 2 (который включает прогресс загрузки) поддерживается начиная с:

  • Chrome 7+
  • Firefox 3.5+
  • Safari 5+
  • Edge (все версии)

Для старых браузеров загрузка всё равно работает — пользователи просто не увидят обновления прогресса. Форма корректно деградирует до стандартной загрузки файла.

Заключение

Создание доступного индикатора прогресса загрузки требует XMLHttpRequest для событий прогресса, семантического HTML для структуры и продуманного JavaScript для обработки различных состояний загрузки. Эта реализация обеспечивает обратную связь в реальном времени, которая работает для всех пользователей, включая тех, кто использует вспомогательные технологии, сохраняя при этом широкую совместимость с браузерами без необходимости в каких-либо внешних библиотеках.

Часто задаваемые вопросы

Fetch API не поддерживает события прогресса загрузки. XMLHttpRequest предоставляет обработчик события xhr.upload.onprogress специально для отслеживания прогресса загрузки, что делает его единственным жизнеспособным вариантом для индикаторов прогресса в реальном времени.

Создайте отдельные экземпляры XMLHttpRequest для каждого файла или поставьте их в очередь последовательно. Отслеживайте индивидуальный прогресс и вычисляйте общий прогресс, усредняя проценты или суммируя загруженные байты по всем файлам.

Без заголовков Content-Length свойство event.lengthComputable возвращает false. Покажите неопределённое состояние прогресса, удалив атрибут value элемента progress и отображая общий текст загрузки вместо процентов.

Стандартный XMLHttpRequest не поддерживает возобновляемые загрузки. Для этой функциональности реализуйте загрузку по частям с серверной поддержкой или используйте специализированные библиотеки, которые обрабатывают логику разделения файлов и возобновления.

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