Bootstrap 4 Drag And Drop File Upload

In this article, we’ll be using “vanilla” ES2015+ JavaScript (no frameworks or libraries) to lớn complete this project, và it is assumed you have a working knowledge of JavaScript in the browser. This example should be compatible with every evergreen browser plus IE 10 and 11.

Bạn đang xem: Bootstrap 4 drag and drop file upload

It’s a known fact that tệp tin selection inputs are difficult to style the way developers want khổng lồ, so many simply hide it & create a button that opens the tệp tin selection dialog instead. Nowadays, though, we have an even fancier way of handling tệp tin selection: drag & drop.

Technically, this was already possible because most (if not all) implementations of the file selection đầu vào allowed you khổng lồ drag files over it khổng lồ select them, but this requires you lớn actually show the file element. So, let’s actually use the APIs given lớn us by the browser to lớn implement a drag-and-drop file selector và uploader.

In this article, we’ll be using “vanilla” ES2015+ JavaScript (no frameworks or libraries) to lớn complete this project, và it is assumed that you have a working knowledge of JavaScript in the browser. This example — aside from the ES2015+ syntax, which can easily changed to lớn ES5 syntax or transpiled by Babel — should be compatible with every evergreen browser plus IE 10 and 11.

Here’s a quiông chồng look at what you’ll be making:

Drag-and-drop image uploader in actionA demonstration of a website page in which you can upload images via drag và drop, pĐánh Giá the images being uploaded immediately, & see the progress of the upload in a progress bar.

Drag-and-Drop Events

The first thing we need lớn discuss is the events related khổng lồ drag-and-drop because they are the driving force behind this feature. In all, there are eight events the browser fires related to drag & drop: drag, dragkết thúc, dragenter, dragexit, dragleave sầu, dragover, dragstart, & drop. We won’t be going over all of them because drag, dragend, dragexit, và dragstart are all fired on the element that is being dragged, & in our case, we’ll be dragging files in from our file system rather than DOM elements, so these events will never pop up.

If you’re curious about them, you can read some documentation about these events on MDN.

As you might expect, you can register event handlers for these events in the same way you register sự kiện handlers for most browser events: via addEventListener.

let dropArea = document.getElementById("drop-area") dropArea.addEventListener("dragenter", handlerFunction, false) dropArea.addEventListener("dragleave", handlerFunction, false) dropArea.addEventListener("dragover", handlerFunction, false) dropArea.addEventListener("drop", handlerFunction, false)
Here’s a little table describing what these events do, using dropArea from the code sample in order to lớn make the language clearer:

EventWhen Is It Fired?
dragenterThe dragged thắng lợi is dragged over dropArea, making it the target for the drop event if the user drops it there.
dragleaveThe dragged vật phẩm is dragged off of dropArea and onlớn another element, making it the target for the drop sự kiện instead.
dragoverEvery few hundred milliseconds, while the dragged công trình is over dropArea & is moving.
dropThe user releases their mouse button, dropping the dragged thành tích onto dropArea.

lưu ý that the dragged item is dragged over a child of dropArea, dragleave will fire on dropArea & dragenter will fire on that child element because it is the new target. The drop event will propagate up lớn dropArea (unless propagation is stopped by a different sự kiện listener before it gets there), so it’ll still fire on dropArea despite it not being the target for the event.

Also note that in order khổng lồ create custom drag-and-drop interactions, you’ll need to lớn Gọi sự kiện.preventDefault() in each of the listeners for these events. If you don’t, the browser will over up opening the tệp tin you dropped instead of sending it along to lớn the drop sự kiện handler.

Setting Up Our Form

Before we start adding drag-and-drop functionality, we’ll need a basic khung with a standard tệp tin đầu vào. Technically this isn’t necessary, but it’s a good idea to provide it as an alternative in case the user has a browser without tư vấn for the drag-and-drop API.

Xem thêm: 10 Dấu Hiệu Cảnh Báo Bệnh Ung Thư Máu Dấu Hiệu, Các Dấu Hiệu Cảnh Báo Ung Thư Máu

Upload multiple files with the file dialog or by dragging và dropping images onlớn the dashed region

Select some files

Pretty simple structure. You may notice an onchange handler on the input. We’ll take a look at that later. It would also be a good idea lớn add an action to the form and a submit button khổng lồ help out those people who don’t have sầu JavaScript enabled. Then you can use JavaScript lớn get rid of them for a cleaner form. In any case, you will need a server-side script khổng lồ accept the upload, whether it’s something developed in-house, or you’re using a service like Cloudinary lớn bởi it for you. Other than those notes, there’s nothing special here, so let’s throw some styles in:

#drop-area border: 2px dashed #ccc; border-radius: 20px; width: 480px; font-family: sans-serif; margin: 100px auto; padding: 20px;#drop-area.highlight border-color: purple;p margin-top: 0;.my-khung margin-bottom: 10px;#gallery margin-top: 10px;#gallery img width: 150px; margin-bottom: 10px; margin-right: 10px; vertical-align: middle;.button display: inline-block; padding: 10px; background: #ccc; cursor: pointer; border-radius: 5px; border: 1px solid #ccc;.button:hover background: #ddd;#fileElem display: none;Many of these styles aren’t coming inkhổng lồ play yet, but that’s OK. The highlights, for now, are that the tệp tin đầu vào is hidden, but its label is styled to lớn look lượt thích a button, so people will realize they can cliông chồng it to lớn bring up the file selection dialog. We’re also following a convention by outlining the drop area with dashed lines.

Adding The Drag-and-Drop Functionality

Now we get to lớn the meat of the situation: drag & drop. Let’s throw a script in at the bottom of the page, or in a separate file, however you feel lượt thích doing it. The first thing we need in the script is a reference to the drop area so we can attach some events to it:

let dropArea = document.getElementById("drop-area")
Now let’s add some events. We’ll start off with adding handlers to lớn all the events to prsự kiện mặc định behaviors & stop the events from bubbling up any higher than necessary:

;<"dragenter", "dragover", "dragleave", "drop">.forEach(eventName => dropArea.addEventListener(eventName, preventDefaults, false))function preventDefaults (e) e.preventDefault() e.stopPropagation()
Now let’s add an indicator to lớn let the user know that they have indeed dragged the thành phầm over the correct area by using CSS khổng lồ change the color of the border color of the drop area. The styles should already be there under the #drop-area.highlight selector, so let’s use JS to lớn add và remove that highlight class when necessary.

;<"dragenter", "dragover">.forEach(eventName => dropArea.addEventListener(eventName, highlight, false));<"dragleave", "drop">.forEach(eventName => dropArea.addEventListener(eventName, unhighlight, false))function highlight(e) dropArea.classList.add("highlight")function unhighlight(e) dropArea.classList.remove("highlight")
We had to lớn use both dragenter and dragover for the highlighting because of what I mentioned earlier. If you start off hovering directly over dropArea và then hover over one of its children, then dragleave sầu will be fired & the highlight will be removed. The dragover sự kiện is fired after the dragenter và dragleave sầu events, so the highlight will be added baông xã onlớn dropArea before we see it being removed.

We also remove sầu the highlight when the dragged công trình leaves the designated area or when you drop the công trình.

Now all we need lớn vị is figure out what lớn vì when some files are dropped:

dropArea.addEventListener("drop", handleDrop, false)function handleDrop(e) let dt = e.dataTransfer let files = dt.files handleFiles(files)
This doesn’t bring us anywhere near completion, but it does two important things:

Demonstrates how lớn get the data for the files that were dropped.Gets us khổng lồ the same place that the tệp tin đầu vào was at with its onchange handler: waiting for handleFiles.

Keep in mind that files is not an array, but a FileList. So, when we implement handleFiles, we’ll need to convert it lớn an array in order lớn iterate over it more easily:

function handleFiles(files) (<...files>).forEach(uploadFile)
That was anticlimactic. Let’s get inkhổng lồ uploadFile for the real meaty stuff.

function uploadFile(file) let url = "YOUR URL HERE" let formData = new FormData() formData.append("file", file) fetch(url, method: "POST", body: formData ) .then(() => /* Done. Inform the user */ ) .catch(() => /* Error. Insize the user */ )
Here we use FormData, a built-in browser API for creating form data to send to lớn the VPS. We then use the fetch API to lớn actually send the image to the VPS. Make sure you change the URL to work with your back-kết thúc or service, & formData.appover any additional size data you may need to give the hệ thống all the information it needs.Alternatively, if you want khổng lồ tư vấn Internet Explorer, you may want khổng lồ use XMLHttpRequest, which means uploadFile would look like this instead:

function uploadFile(file) var url = "YOUR URL HERE" var xhr = new XMLHttpRequest() var formData = new FormData()"POST", url, true) xhr.addEventListener("readystatechange", function(e) if (xhr.readyState == 4 && xhr.status == 200) // Done. Insize the user else if (xhr.readyState == 4 && xhr.status != 200) // Error. Insize the user ) formData.append("file", file) xhr.send(formData)
Depending on how your server is phối up, you may want to kiểm tra for different ranges of status numbers rather than just 200, but for our purposes, this will work.

Additional Features

That is all of the base functionality, but often we want more functionality. Specifically, in this tutorial, we’ll be adding a pđánh giá pane that displays all the chosen images to lớn the user, then we’ll add a progress bar that lets the user see the progress of the uploads. So, let’s get started with previewing images.

Image Preview

There are a couple of ways you could vì this: you could wait until after the image has been uploaded & ask the server lớn skết thúc the URL of the image, but that means you need khổng lồ wait và images can be pretty large sometimes. The alternative — which we’ll be exploring today — is to use the FileReader API on the tệp tin data we received from the drop event. This is asynchronous, và you could alternatively use FileReaderSync, but we could be trying to lớn read several large files in a row, so this could bloông xã the thread for quite a while & really ruin the experience. So let’s create a previewFile function & see how it works:

function previewFile(file) let reader = new FileReader() reader.readAsDataURL(file) reader.onloadkết thúc = function() let img = document.createElement("img") img.src = reader.result document.getElementById("gallery").appendChild(img)
Here we create a new FileReader & điện thoại tư vấn readAsDataURL on it with the File object. As mentioned, this is asynchronous, so we need khổng lồ add an onloadend sự kiện handler in order to get the result of the read. We then use the base 64 data URL as the src for a new image element và add it to the gallery element. There are only two things that need khổng lồ be done khổng lồ make this work now: add the gallery element, & make sure previewFile is actually called.

First, add the following HTML right after the over of the khung tag:

There are a few ways you could have done this, such as composition, or a single callbachồng khổng lồ forEach that ran uploadFile & previewFile in it, but this works too. And with that, when you drop or select some images, they should show up almost instantly below the khung. The interesting thing about this is that — in certain applications — you may not actually want lớn upload images, but instead store the data URLs of them in localStorage or some other client-side cađậy lớn be accessed by the tiện ích later. I can’t personally think of any good use cases for this, but I’m willing to lớn bet there are some.

Tracking Progress

If something might take a while, a progress bar can help a user realize progress is actually being made & give sầu an indication of how long it will take lớn be completed. Adding a progress indicator is pretty easy thanks to lớn the HTML5 progress tag. Let’s start by adding that khổng lồ the HTML code this time.

You can plop that in right after the label or between the form và gallery div, whichever you fancy more. For that matter, you can place it wherever you want within the body toàn thân tags. No styles were added for this example, so it will show the browser’s mặc định implementation, which is serviceable. Now let’s work on adding the JavaScript. We’ll first look at the implementation using fetch and then we’ll show a version for XMLHttpRequest. To start, we’ll need a couple of new variables at the top of the script :

let filesDone = 0let filesToDo = 0let progressBar = document.getElementById("progress-bar")
When using fetch we’re only able to lớn determine when an upload is finished, so the only information we traông chồng is how many files are selected khổng lồ upload (as filesToDo) and the number of files that have finished uploading (as filesDone). We’re also keeping a reference to lớn the #progress-bar element so we can update it quickly. Now let’s create a couple of functions for managing the progress:

function initializeProgress(numfiles) progressBar.value = 0 filesDone = 0 filesToDo = numfilesfunction progressDone() filesDone++ progressBar.value = filesDone / filesToDo * 100
When we start uploading, initializeProgress will be called khổng lồ rephối the progress bar. Then, with each completed upload, we’ll Gọi progressDone lớn increment the number of completed uploads và update the progress bar to show the current progress. So let’s điện thoại tư vấn these functions by updating a couple of old functions:

function handleFiles(files) files = <...files> initializeProgress(files.length) // /* Error. Inform the user */ )
And that’s it. Now let’s take a look at the XMLHttpRequest implementation. We could just make a quiông xã update lớn uploadFile, but XMLHttpRequest actually gives us more functionality than fetch, namely we’re able lớn add an event listener for upload progress on each request, which will periodically give sầu us information about how much of the request is finished. Because of this, we need to lớn track the percentage completion of each request instead of just how many are done. So, let’s start with replacing the declarations for filesDone and filesToDo with the following:

let uploadProgress = <>Then we need khổng lồ update our functions as well. We’ll rename progressDone to updateProgress and change them to be the following:

function initializeProgress(numFiles) progressBar.value = 0 uploadProgress = <> for(let i = numFiles; i > 0; i--) uploadProgress.push(0) function updateProgress(fileNumber, percent) uploadProgress = percent let total = uploadProgress.reduce((tot, curr) => tot + curr, 0) / uploadProgress.length progressBar.value = total
Now initializeProgress initializes an array with a length equal to lớn numFiles that is filled with zeroes, denoting that each tệp tin is 0% complete. In updateProgress we find out which image is having their progress updated and change the value at that index khổng lồ the provided percent. We then calculate the total progress percentage by taking an average of all the percentages and update the progress bar to reflect the calculated total.We still Call initializeProgress in handleFiles the same as we did in the fetch example, so now all we need to update is uploadFile to Gọi updateProgress.

function uploadFile(file, i) { //
The first thing to lớn note is that we added an i parameter. This is the index of the file in the danh sách of files. We don’t need to lớn update handleFiles lớn pass this parameter in because it is using forEach, which already gives the index of the element as the second parameter to lớn callbacks. We also added the progress event listener to xhr.upload so we can Gọi updateProgress with the progress. The sự kiện object (referred khổng lồ as e in the code) has two pertinent pieces of information on it: loaded which contains the number of bytes that have been uploaded so far và total which contains the number of bytes the tệp tin is in total.

The || 100 piece is in there because sometimes if there is an error, e.loaded and will be zero, which means the calculation will come out as NaN, so the 100 is used instead to report that the file is done. You could also use 0. In either case, the error will show up in the readystatechange handler so that you can inkhung the user about them. This is merely khổng lồ prevent exceptions from being thrown for trying lớn bởi vì math with NaN.


That’s the final piece. You now have sầu a website page where you can upload images via drag & drop, pReview the images being uploaded immediately, và see the progress of the upload in a progress bar. You can see the final version (with XMLHttpRequest) in action on CodePen, but be aware that the service I upload the files khổng lồ has limits, so if a lot of people chạy thử it out, it may break for a time.