Using files from web applications
Note: This feature is available inWeb Workers.
Using the File API, web content can ask the user to select local files and then read the contents of those files. This selection can be done by either using an HTML<input type="file">
element or by drag and drop.
Accessing selected file(s)
Consider this HTML:
<input type="file" multiple />
The File API makes it possible to access aFileList
containingFile
objects representing the files selected by the user.
Themultiple
attribute on theinput
element allows the user to select multiple files.
Accessing the first selected file using a classical DOM selector:
const selectedFile = document.getElementById("input").files[0];
Accessing selected file(s) on a change event
It is also possible (but not mandatory) to access theFileList
through thechange
event. You need to useEventTarget.addEventListener()
to add thechange
event listener, like this:
const inputElement = document.getElementById("input");inputElement.addEventListener("change", handleFiles, false);function handleFiles() { const fileList = this.files; /* now you can work with the file list */}
Getting information about selected file(s)
TheFileList
object provided by the DOM lists all of the files selected by the user, each specified as aFile
object. You can determine how many files the user selected by checking the value of the file list'slength
attribute:
const numFiles = fileList.length;
IndividualFile
objects can be retrieved by accessing the list as an array.
There are three attributes provided by theFile
object that contain useful information about the file.
Example: Showing file(s) size
The following example shows a possible use of thesize
property:
<form name="uploadForm"> <div> <input type="file" multiple /> <label for="fileNum">Selected files:</label> <output>0</output>; <label for="fileSize">Total size:</label> <output>0</output> </div> <div><input type="submit" value="Send file" /></div></form>
const uploadInput = document.getElementById("uploadInput");uploadInput.addEventListener( "change", () => { // Calculate total size let numberOfBytes = 0; for (const file of uploadInput.files) { numberOfBytes += file.size; } // Approximate to the closest prefixed unit const units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; const exponent = Math.min( Math.floor(Math.log(numberOfBytes) / Math.log(1024)), units.length - 1, ); const approx = numberOfBytes / 1024 ** exponent; const output = exponent === 0 ? `${numberOfBytes} bytes` : `${approx.toFixed(3)} ${units[exponent]} (${numberOfBytes} bytes)`; document.getElementById("fileNum").textContent = uploadInput.files.length; document.getElementById("fileSize").textContent = output; }, false,);
Using hidden file input elements using the click() method
You can hide the admittedly ugly file<input>
element and present your own interface for opening the file picker and displaying which file or files the user has selected. You can do this by styling the input element withdisplay:none
and calling theclick()
method on the<input>
element.
Consider this HTML:
<input type="file" multiple accept="image/*" /><button type="button">Select some files</button>
#fileElem { display: none;}
The code that handles theclick
event can look like this:
const fileSelect = document.getElementById("fileSelect");const fileElem = document.getElementById("fileElem");fileSelect.addEventListener( "click", (e) => { if (fileElem) { fileElem.click(); } }, false,);
You can style the<button>
however you wish.
Using a label element to trigger a hidden file input element
To allow opening the file picker without using JavaScript (the click() method), a<label>
element can be used. Note that in this case the input element must not be hidden usingdisplay: none
(norvisibility: hidden
), otherwise the label would not be keyboard-accessible. Use thevisually-hidden technique instead.
Consider this HTML:
<input type="file" multiple accept="image/*" /><label for="fileElem">Select some files</label>
and this CSS:
.visually-hidden { clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px;}input.visually-hidden:is(:focus, :focus-within) + label { outline: thin dotted;}
There is no need to add JavaScript code to callfileElem.click()
. Also in this case you can style the label element as you wish. You need to provide a visual cue for the focus status of the hidden input field on its label, be it an outline as shown above, or background-color or box-shadow. (As of time of writing, Firefox doesn't show this visual cue for<input type="file">
elements.)
Selecting files using drag and drop
You can also let the user drag and drop files into your web application.
The first step is to establish a drop zone. Exactly what part of your content will accept drops may vary depending on the design of your application, but making an element receive drop events is easy:
let dropbox;dropbox = document.getElementById("dropbox");dropbox.addEventListener("dragenter", dragenter, false);dropbox.addEventListener("dragover", dragover, false);dropbox.addEventListener("drop", drop, false);
In this example, we're turning the element with the IDdropbox
into our drop zone. This is done by adding listeners for thedragenter
,dragover
, anddrop
events.
We don't actually need to do anything with thedragenter
anddragover
events in our case, so these functions are both simple. They just stop propagation of the event and prevent the default action from occurring:
function dragenter(e) { e.stopPropagation(); e.preventDefault();}function dragover(e) { e.stopPropagation(); e.preventDefault();}
The real magic happens in thedrop()
function:
function drop(e) { e.stopPropagation(); e.preventDefault(); const dt = e.dataTransfer; const files = dt.files; handleFiles(files);}
Here, we retrieve thedataTransfer
field from the event, pull the file list out of it, and then pass that tohandleFiles()
. From this point on, handling the files is the same whether the user used theinput
element or drag and drop.
Example: Showing thumbnails of user-selected images
Let's say you're developing the next great photo-sharing website and want to use HTML to display thumbnail previews of images before the user actually uploads them. You can establish your input element or drop zone as discussed previously and have them call a function such as thehandleFiles()
function below.
function handleFiles(files) { for (const file of files) { if (!file.type.startsWith("image/")) { continue; } const img = document.createElement("img"); img.classList.add("obj"); img.file = file; preview.appendChild(img); // Assuming that "preview" is the div output where the content will be displayed. const reader = new FileReader(); reader.onload = (e) => { img.src = e.target.result; }; reader.readAsDataURL(file); }}
Here our loop handling the user-selected files looks at each file'stype
attribute to see if its MIME type begins withimage/
). For each file that is an image, we create a newimg
element. CSS can be used to establish any pretty borders or shadows and to specify the size of the image, so that doesn't need to be done here.
Each image has the CSS classobj
added to it, making it easy to find in the DOM tree. We also add afile
attribute to each image specifying theFile
for the image; this will let us fetch the images for actual upload later. We useNode.appendChild()
to add the new thumbnail to the preview area of our document.
Next, we establish theFileReader
to handle asynchronously loading the image and attaching it to theimg
element. After creating the newFileReader
object, we set up itsonload
function and then callreadAsDataURL()
to start the read operation in the background. When the entire contents of the image file are loaded, they are converted into adata:
URL which is passed to theonload
callback. Our implementation of this routine sets theimg
element'ssrc
attribute to the loaded image which results in the image appearing in the thumbnail on the user's screen.
Using object URLs
The DOMURL.createObjectURL()
andURL.revokeObjectURL()
methods let you create simple URL strings that can be used to reference any data that can be referred to using a DOMFile
object, including local files on the user's computer.
When you have aFile
object you'd like to reference by URL from HTML, you can create an object URL for it like this:
const objectURL = window.URL.createObjectURL(fileObj);
The object URL is a string identifying theFile
object. Each time you callURL.createObjectURL()
, a unique object URL is created even if you've created an object URL for that file already. Each of these must be released. While they are released automatically when the document is unloaded, if your page uses them dynamically you should release them explicitly by callingURL.revokeObjectURL()
:
URL.revokeObjectURL(objectURL);
Example: Using object URLs to display images
This example uses object URLs to display image thumbnails. In addition, it displays other file information including their names and sizes.
The HTML that presents the interface looks like this:
<input type="file" multiple accept="image/*" /><a href="#">Select some files</a><div> <p>No files selected!</p></div>
#fileElem { display: none;}
This establishes our file<input>
element as well as a link that invokes the file picker (since we keep the file input hidden to prevent that less-than-attractive user interface from being displayed). This is explained in the sectionUsing hidden file input elements using the click() method, as is the method that invokes the file picker.
ThehandleFiles()
method follows:
const fileSelect = document.getElementById("fileSelect"), fileElem = document.getElementById("fileElem"), fileList = document.getElementById("fileList");fileSelect.addEventListener( "click", (e) => { if (fileElem) { fileElem.click(); } e.preventDefault(); // prevent navigation to "#" }, false,);fileElem.addEventListener("change", handleFiles, false);function handleFiles() { fileList.textContent = ""; if (!this.files.length) { const p = document.createElement("p"); p.textContent = "No files selected!"; fileList.appendChild(p); } else { const list = document.createElement("ul"); fileList.appendChild(list); for (const file of this.files) { const li = document.createElement("li"); list.appendChild(li); const img = document.createElement("img"); img.src = URL.createObjectURL(file); img.height = 60; li.appendChild(img); const info = document.createElement("span"); info.textContent = `${file.name}: ${file.size} bytes`; li.appendChild(info); } }}
This starts by fetching the URL of the<div>
with the IDfileList
. This is the block into which we'll insert our file list, including thumbnails.
If theFileList
object passed tohandleFiles()
is empty, we set the inner HTML of the block to display "No files selected!". Otherwise, we start building our file list, as follows:
- A new unordered list (
<ul>
) element is created. - The new list element is inserted into the
<div>
block by calling itsNode.appendChild()
method. - For each
File
in theFileList
represented byfiles
:- Create a new list item (
<li>
) element and insert it into the list. - Create a new image (
<img>
) element. - Set the image's source to a new object URL representing the file, using
URL.createObjectURL()
to create the blob URL. - Set the image's height to 60 pixels.
- Append the new list item to the list.
- Create a new list item (
Here is a live demo of the code above:
Note that we don't immediately revoke the object URL after the image has loaded, because doing so would make the image unusable for user interactions (such as right-clicking to save the image or opening it in a new tab). For long-lived applications, you should revoke object URLs when they're no longer needed (such as when the image is removed from the DOM) to free up memory by calling theURL.revokeObjectURL()
method and passing in the object URL string.
Example: Uploading a user-selected file
This example shows how to let the user upload files (such as the images selected using the previous example) to a server.
Note:It's usually preferable to make HTTP requests using theFetch API instead ofXMLHttpRequest
. However, in this case we want to show the user the upload progress, and this feature is still not supported by the Fetch API, so the example usesXMLHttpRequest
.
Work to track standardization of progress notifications using the Fetch API is athttps://github.com/whatwg/fetch/issues/607.
Creating the upload tasks
Continuing with the code that built the thumbnails in the previous example, recall that every thumbnail image is in the CSS classobj
with the correspondingFile
attached in afile
attribute. This allows us to select all of the images the user has chosen for uploading usingDocument.querySelectorAll()
, like this:
function sendFiles() { const imgs = document.querySelectorAll(".obj"); for (const img of imgs) { new FileUpload(img, img.file); }}
document.querySelectorAll
fetches aNodeList
of all the elements in the document with the CSS classobj
. In our case, these will be all of the image thumbnails. Once we have that list, it's trivial to go through it and create a newFileUpload
instance for each. Each of these handles uploading the corresponding file.
Handling the upload process for a file
TheFileUpload
function accepts two inputs: an image element and a file from which to read the image data.
function FileUpload(img, file) { const reader = new FileReader(); this.ctrl = createThrobber(img); const xhr = new XMLHttpRequest(); this.xhr = xhr; this.xhr.upload.addEventListener( "progress", (e) => { if (e.lengthComputable) { const percentage = Math.round((e.loaded * 100) / e.total); this.ctrl.update(percentage); } }, false, ); xhr.upload.addEventListener( "load", (e) => { this.ctrl.update(100); const canvas = this.ctrl.ctx.canvas; canvas.parentNode.removeChild(canvas); }, false, ); xhr.open( "POST", "https://demos.hacks.mozilla.org/paul/demos/resources/webservices/devnull.php", ); xhr.overrideMimeType("text/plain; charset=x-user-defined-binary"); reader.onload = (evt) => { xhr.send(evt.target.result); }; reader.readAsBinaryString(file);}function createThrobber(img) { const throbberWidth = 64; const throbberHeight = 6; const throbber = document.createElement("canvas"); throbber.classList.add("upload-progress"); throbber.setAttribute("width", throbberWidth); throbber.setAttribute("height", throbberHeight); img.parentNode.appendChild(throbber); throbber.ctx = throbber.getContext("2d"); throbber.ctx.fillStyle = "orange"; throbber.update = (percent) => { throbber.ctx.fillRect( 0, 0, (throbberWidth * percent) / 100, throbberHeight, ); if (percent === 100) { throbber.ctx.fillStyle = "green"; } }; throbber.update(0); return throbber;}
TheFileUpload()
function shown above creates a throbber, which is used to display progress information, and then creates anXMLHttpRequest
to handle uploading the data.
Before actually transferring the data, several preparatory steps are taken:
- The
XMLHttpRequest
's uploadprogress
listener is set to update the throbber with new percentage information so that as the upload progresses the throbber will be updated based on the latest information. - The
XMLHttpRequest
's uploadload
event handler is set to update the throbber progress information to 100% to ensure the progress indicator actually reaches 100% (in case of granularity quirks during the process). It then removes the throbber since it's no longer needed. This causes the throbber to disappear once the upload is complete. - The request to upload the image file is opened by calling
XMLHttpRequest
'sopen()
method to start generating a POST request. - The MIME type for the upload is set by calling the
XMLHttpRequest
functionoverrideMimeType()
. In this case, we're using a generic MIME type; you may or may not need to set the MIME type at all depending on your use case. - The
FileReader
object is used to convert the file to a binary string. - Finally, when the content is loaded the
XMLHttpRequest
functionsend()
is called to upload the file's content.
Asynchronously handling the file upload process
This example, which uses PHP on the server side and JavaScript on the client side, demonstrates asynchronous uploading of a file.
<?phpif (isset($_FILES["myFile"])) { // Example: move_uploaded_file($_FILES["myFile"]["tmp_name"], "uploads/" . $_FILES["myFile"]["name"]); exit;}?><!doctype html><html lang="en-US"> <head> <meta charset="UTF-8" /> <title>dnd binary upload</title> <script> function sendFile(file) { const uri = "/index.php"; const xhr = new XMLHttpRequest(); const fd = new FormData(); xhr.open("POST", uri, true); xhr.onreadystatechange = () => { if (xhr.readyState === 4 && xhr.status === 200) { alert(xhr.responseText); // handle response. } }; fd.append("myFile", file); // Initiate a multipart/form-data upload xhr.send(fd); } window.onload = () => { const dropzone = document.getElementById("dropzone"); dropzone.ondragover = dropzone.ondragenter = (event) => { event.stopPropagation(); event.preventDefault(); }; dropzone.ondrop = (event) => { event.stopPropagation(); event.preventDefault(); const filesArray = event.dataTransfer.files; for (let i = 0; i < filesArray.length; i++) { sendFile(filesArray[i]); } }; }; </script> </head> <body> <div> <div > Drag & drop your file here </div> </div> </body></html>
Example: Using object URLs to display PDF
Object URLs can be used for other things than just images! They can be used to display embedded PDF files or any other resources that can be displayed by the browser.
In Firefox, to have the PDF appear embedded in the iframe (rather than proposed as a downloaded file), the preferencepdfjs.disabled
must be set tofalse
.
<iframe></iframe>
And here is the change of thesrc
attribute:
const objURL = URL.createObjectURL(blob);const iframe = document.getElementById("viewer");iframe.setAttribute("src", objURL);// Later:URL.revokeObjectURL(objURL);
Example: Using object URLs with other file types
You can manipulate files of other formats the same way. Here is how to preview uploaded video:
const video = document.getElementById("video");const objURL = URL.createObjectURL(blob);video.src = objURL;video.play();// Later:URL.revokeObjectURL(objURL);