Upload progress bar using XHR (Fetch alternative)
Last updated: September 27, 2022.
At the time of writing, you cannot track the upload progress of a POST or PUT request using the Fetch API.
A native alternative is to use the XMLHttpRequest
(XHR) object.
Table of contents
HTML and element selection
First, create an input element of type file
so a user can select a local file and an upload button.
Then, create a progress bar with a <progress>
element. For percentage progress, set the value
attribute to 0 and the max
attribute to 100.
Select the input element, upload button and progress bar in JavaScript.
<!-- Step 1 of 2: HTML and element selection -->
<!-- Upload -->
<div class="container">
<input type="file" accept=".png, .jpg" id="file">
<button id="upload-btn">Upload a file</button>
</div>
<!-- Progress bar -->
<div class="container">
<progress id="progress-bar" value="0" max="100"></progress><br>
<label for="progress-bar">0%</label>
</div>
<script>
const file = document.getElementById('file');
const btn = document.getElementById('upload-btn');
const progress = document.getElementById('progress-bar');
</script>
Handling the upload with the XMLHttpRequest
object
Inside a click
event listener attached to the upload button, you need to select the file.
Optionally, you can append it to a FormData
object. This is generally recommended because it will automatically set request headers for you.
Then, you post the payload by creating by making a POST or PUT request using the XMLHttpRequest
object.
To measure upload progress, attach a progress
event listener to the upload
object on the request. Every time there is upload progress, its callback function is fire. You can use information available on the available event object to update the DOM.
The server response is handled inside a callback function listening out for a load
event on the request.
/* Step 2 of 2: Handling upload with upload progress updates */
btn.addEventListener('click', function() {
// Select user file from file list (always position 0 for 1 files)
const userImg = file.files[0];
// Embed file to FormData object, which will automatically set request headers
const payload = new FormData();
payload.append('user-image', userImg, 'user-image.jpg'); // (key, data, new filename)
/* A POST request using XML object */
const req = new XMLHttpRequest(); // Initialize request
req.open('POST', 'https://httpbin.org/post'); // Specify request type and endpoint
// Add event listener to upload listening for progress. Function fires
// regularly, with progress contained in "e" object
req.upload.addEventListener('progress', function(e) {
// Every time progress occurs
const percentComplete = (e.loaded / e.total)*100; // Calculate percentage complete via "e" object
progress.setAttribute('value', percentComplete); // Update value of progress HTML element
progress.nextElementSibling.nextElementSibling.innerText = Math.round(percentComplete)+"%"; // Prints progress in progress element label as well
})
// Fires when upload is complete
req.addEventListener('load', function() {
console.log(req.status); // Response status code
console.log(req.response); // Request response
})
req.send(payload); // Sends request
});
Testing tips
You may find that the upload progress bar only updates once: from 0 to 100.
This can be because the file upload occurs very quickly.
To simulate a slow internet connection, try adding 'throttling' in the network tab of your browser's developer tools.
With this, you have a better opportunity to see the progress bar in action.
Full code example
Here's the full code example with explanatory comments removed:
<!-- User file input -->
<div class="container">
<input type="file" accept=".png, .jpg" id="file">
<button id="upload-btn">Upload a file</button>
</div>
<!-- Progress bar -->
<div class="container">
<progress id="progress-bar" value="0" max="100"></progress><br>
<label for="progress-bar">0%</label>
</div>
<script>
const file = document.getElementById('file');
const btn = document.getElementById('upload-btn');
const progress = document.getElementById('progress-bar');
btn.addEventListener('click', function() {
/* Prepare payload */
const userImg = file.files[0];
const payload = new FormData();
payload.append('user-image', userImg, 'user-image.jpg');
/* XHR POST request */
const req = new XMLHttpRequest();
req.open('POST', 'https://httpbin.org/post');
req.upload.addEventListener('progress', function(e) {
const percentComplete = (e.loaded / e.total)*100;
progress.setAttribute('value', percentComplete);
progress.nextElementSibling.nextElementSibling.innerText = Math.round(percentComplete)+"%";
})
req.addEventListener('load', function() {
console.log(req.status);
console.log(req.response);
})
req.send(payload);
});
</script>
Related links
- MDN Web Docs: XMLHttpRequest
- HTTPBin – A test endpoint for making HTTP requests
- OpenJavaScript: Create an upload progress bar with Axios
- OpenJavaScript: Asynchronous JavaScript: Complete Tutorial