File upload is very ubiquitous to any web application and when it comes to uploading files and resources over the internet (on a browser), things can be somewhat stressful. Fortunately, with HTML 5, input elements which usually come with form control to allow users to modify data can become so handy in simplifying uploading resources.
In this article, we would have a closer look at how to handle file uploads using vanilla JavaScript. The aim is to teach you how to build a file upload component without the need for an external library and also learn some core concepts in JavaScript. You will also learn how to show the progress status of an upload as it happens.
Source Code: As usual, you can tinker with the source code hosted on GitHub repo for the project.
Project Setup
First and foremost, in your preferred directory, create a new folder for the project:
$ mkdir file-upload-progress-bar-javascript
After doing so, let us now create index.html
, main.css
, and app.js
files where we will write all the markup for our project.
$ touch index.html && touch main.css && touch app.js
Now we can begin to build the structure for the file upload by creating a basic HTML template with <head>
and <body>
tags:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>File Upload with Progress Bar using JavaScript</title>
</head>
<body></body>
</html>
Next, we add base styles for the project in main.css
:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
To enhance the look of our applications, we will make use of the icons from the font awesome library which we can add to our project through a kit code that can be created at the official font awesome library website.
Now, index.html
is updated, and the main.css
file is linked:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script
src="https://kit.fontawesome.com/355573397a.js"
crossorigin="anonymous"
></script>
<link rel="stylesheet" href="main.css">
<title>File Upload with Progress Bar using JavaScript</title>
</head>
<body></body>
</html>
We continue by building the structure for the file uploader:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script
src="https://kit.fontawesome.com/355573397a.js"
crossorigin="anonymous"
></script>
<link rel="stylesheet" href="main.css" />
<title>File Upload with Progress Bar using JavaScript</title>
</head>
<body>
<div class="file-upload__wrapper">
<header>File Uploader JavaScript with Progress</header>
<div class="form-parent">
<form action="#" class="file-upload__form">
<input class="file-input" type="file" name="file" hidden />
<i class="fas fa-cloud-upload-alt"></i>
<p>Browse File to Upload</p>
</form>
<div>
<section class="progress-container"></section>
<section class="uploaded-container"></section>
</div>
</div>
</div>
<script src="app.js"></script>
</body>
</html>
Then, copy/paste the following codes to update main.css
:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
background: #cb67e9;
display: flex;
align-items: center;
justify-content: center;
font-family: Arial, Helvetica, sans-serif;
}
::selection {
color: white;
background: #cb67e9;
}
.file-upload__wrapper {
width: 640px;
background: #fff;
border-radius: 5px;
padding: 35px;
box-shadow: 6px 6px 12px rgba(0, 0, 0, 0.05);
}
.file-upload__wrapper header {
color: #cb67e9;
font-size: 2rem;
text-align: center;
margin-bottom: 20px;
}
.form-parent {
display: flex;
align-items: center;
gap: 30px;
justify-content: center;
}
.file-upload__wrapper form.file-upload__form {
height: 150px;
border: 2px dashed #cb67e9;
cursor: pointer;
margin: 30px 0;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
border-radius: 6px;
padding: 10px;
}
form.file-upload__form :where(i, p) {
color: #cb67e9;
}
form.file-upload__form i {
font-size: 50px;
}
form.file-upload__form p {
font-size: 1rem;
margin-top: 15px;
}
section .row {
background: #e9f0ff;
margin-bottom: 10px;
list-style: none;
padding: 15px 12px;
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 6px;
}
section .row i {
font-size: 2rem;
color: #cb67e9;
}
section .details span {
font-size: 1rem;
}
.progress-container .row .content-wrapper {
margin-left: 15px;
width: 100%;
}
.progress-container .details {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 7px;
}
.progress-container .content .progress-bar-wrapper {
height: 10px;
width: 100%;
margin-bottom: 5px;
background: #fff;
border-radius: 30px;
}
.content .progress-bar .progress-wrapper {
height: 100%;
background: #cb67e9;
width: 0%;
border-radius: 6px;
}
.uploaded-container {
overflow-y: scroll;
max-height: 230px;
}
.uploaded-container.onprogress {
max-height: 160px;
}
.uploaded-container .row .content-wrapper {
display: flex;
align-items: center;
}
.uploaded-container .row .details-wrapper {
display: flex;
flex-direction: column;
margin-left: 15px;
}
.uploaded-container .row .details-wrapper .name span {
color: green;
font-size: 10px;
}
.uploaded-container .row .details-wrapper .file-size {
color: #404040;
font-size: 11px;
}
Now, the component should be looking better on the browser:
Adding Upload Functionality
To add the required functionality for uploading in our project, we now make use of the app.js
file, where we write JavaScript codes that give life to our project.
Copy/paste the following into app.js
:
const uploadForm = document.querySelector(".file-upload__form");
const myInput = document.querySelector(".file-input");
const progressContainer = document.querySelector(".progress-container");
const uploadedContainer = document.querySelector(".uploaded-container");
uploadForm.addEventListener("click", () => {
myInput.click();
});
myInput.onchange = ({ target }) => {
let file = target.files[0];
if (file) {
let fileName = file.name;
if (fileName.length >= 12) {
let splitName = fileName.split(".");
fileName = splitName[0].substring(0, 13) + "... ." + splitName[1];
}
uploadFile(fileName);
}
};
function uploadFile(name) {
let xhrRequest = new XMLHttpRequest();
const endpoint = "uploadFile.php";
xhrRequest.open("POST", endpoint);
xhrRequest.upload.addEventListener("progress", ({ loaded, total }) => {
let fileLoaded = Math.floor((loaded / total) * 100);
let fileTotal = Math.floor(total / 1000);
let fileSize;
fileTotal < 1024
? (fileSize = fileTotal + " KB")
: (fileSize = (loaded / (1024 * 1024)).toFixed(2) + " MB");
let progressMarkup = `<li class="row">
<i class="fas fa-file-alt"></i>
<div class="content-wrapper">
<div class="details-wrapper">
<span class="name">${name} | <span>Uploading</span></span>
<span class="percent">${fileLoaded}%</span>
</div>
<div class="progress-bar-wrapper">
<div class="progress-wrapper" style="width: ${fileLoaded}%"></div>
</div>
</div>
</li>`;
uploadedContainer.classList.add("onprogress");
progressContainer.innerHTML = progressMarkup;
if (loaded == total) {
progressContainer.innerHTML = "";
let uploadedMarkup = `<li class="row">
<div class="content-wrapper upload">
<i class="fas fa-file-alt"></i>
<div class="details-wrapper">
<span class="name">${name} | <span>Uploaded</span></span>
<span class="file-size">${fileSize}</span>
</div>
</div>
</li>`;
uploadedContainer.classList.remove("onprogress");
uploadedContainer.insertAdjacentHTML("afterbegin", uploadedMarkup);
}
});
let data = new FormData(uploadForm);
xhrRequest.send(data);
}
What we have done is to be able to read a file that has been selected from using the file input element, and create a new list of files on the DOM. When the file is being uploaded, the progress level is shown, and when the file is completed uploaded, the progress status changes to uploaded.
Then, we also add an uploadFile.php
to our project to mock an endpoint for sending files. The reason for this is to simulate asynchronosity in our project so that we get the progress loading effect.
<?php
$file_name = $_FILES['file']['name'];
$tmp_name = $_FILES['file']['tmp_name'];
$file_up_name = time().$file_name;
move_uploaded_file($tmp_name, "files/".$file_up_name);
?>
Conclusion
You have been awesome to get through to this point of this article.
In this tutorial, you have learned how to build a file upload component and add a progress bar to it. This can be useful when you build websites and want your users to feel included and get a sense of how slow or fast uploading a file is going. Feel free to re-use this project whenever you wish to.
If you get stuck while following along with this tutorial, I suggest you upload your project on GitHub for help from other developers or you can also send a dm to me too, I'll be happy to help.
Here is a link to the GitHub repo for the project.
Top comments (0)