Nime

Working with files on the web

Here is where I would put an intro to an article about using files on the web, IF I HAD ONE.

Getting a File

A File object lets you read a bunch of information about a file, including the contents.

The <input type="file" />

Ol’ reliable, the <input> element.

Give it a type attribute of "file", and a button appears that opens your OS’s file picker when clicked.

An <input type="file" /> has many extra features. It’s a drop zone too. Drop a file on it, and it will behave like you opened that file picker and chose a file. Neat!

After the user selects a file (or multiple) from the file picker, a "change" event is fired. The file(s) will be accessible on event.target.files.

It’s a FileList. Every item inside that list is a File.

If a single file was selected, it’ll be the 0th element in that list.

index.html
<input type="file" />
index.js
const inputEl = document.querySelector("input");
inputEl.addEventListener("change", (event) => {
const file = event.target.files[0];
console.log("A file was selected");
console.log("Name:", file.name);
});

Drop zone

Another method to get a File is using a drop zone. Dragging and dropping a file is a nice user experience.

Each handler calls event.preventDefault() to prevent further processing of the event.

You also might want to call event.stopPropagation() in each handler. It’s not required, but allowing the events to continue propagating can lead to unwanted behaviour.

I think it’s a bit weird too, but it’s necessary.

The file(s) are accessible under event.dataTransfer.files in the "drop" event handler.

Alternatively, you can use the DataTransferItemList API to access the file(s). This API is handy if you also want to support the dropping of things that are not File objects.

index.html
<div>Welcome to THE DROP ZONE</div>
index.js
const dropZoneEl = document.querySelector("div");
dropZoneEl.addEventListener("dragover", (event) => {
event.preventDefault();
});
dropZoneEl.addEventListener("drop", (event) => {
event.preventDefault();
console.log("Something was dropped");
const file = event.dataTransfer.files[0];
console.log("Name:", file.name);
});

The The File System Access API

The File System Access API is fairly new at the time of writing and available in Chromium browsers (Chrome and Edge).

It differs from the previous methods in that you first get a file handle, which in turn lets you get the underlying file.

A benefit of this is that you can use that handle to also write to the file.

You get one (or multiple) FileSystemFileHandle(s) by calling a method on the window.

This opens the OS’s file picker and returns a Promise that resolves to an array of handles to the file(s) you just picked.
The file itself can then be accessed by calling the getFile method on a handle.
That method returns a Promise that resolves to a File.

index.js
const fileHandles = await window.showOpenFilePicker();
const file = await fileHandles[0].getFile();
console.log("A file was selected");
console.log("Name:", file.name);

Using a File

A File is a special kind of Blob and can be used everywhere a Blob can.

The metadata directly accessible on a File object is browser-specific, what might be there in one browser, may not exist in another.

By metadata I mean properties like file.name, file.type, file.size, …

The most interesting thing is the contents of that file. This being the web, there are multiple ways to read that data.

The File API

The File object inherits a bunch of methods from Blob that can retrieve all the data-y goodness your file holds.

For example .text() and .arrayBuffer() return a Promise that resolves to a string or an arraybuffer respectively. While .stream() returns a ReadableStream.

Lots of options!

  • Is the file an image and do you want to draw it in a <canvas>? No problem! Call createImageBitmap(file) and feed it to the canvas’ drawImage method.

  • Do you want to represent the data as a Data URL? Some example usages of those is populating the src attribute of a <video> or <img> tag.

  • The URL API has a createObjectURL method that accepts a Blob!

The FileReader API

The FileReader API is an event based API. A reader fires events you can listen to by defining an addEventListener.

You can kick off an action for the reader to take by calling one of its methods: readAsText, readAsArrayBuffer, readAsDataURL, readAsText, or readAsBinaryString.

Once that action completes, the reader will fire the "load" event, and the result will be available on reader.result.

This event-based API allows a bit more flexibility than using the Promises of the File API, like showing a progress bar by listening to the "progress" event the reader will periodically fire while reading a file.

index.js
const file = /* a File object */;
const reader = new FileReader();
reader.addEventListener("load", (event) => {
console.log("Successfully read:", file.name);
console.log("Text contents:", reader.result);
})
reader.readAsText(file);

Writing to files

Once you have a handle to a file, you can call the createWritable method to get a FileSystemWritableFileStream, which can be used to write to that file.

The snippet below writes “BOOP” to a file. Glorious.

index.html
<button>choose a file to write to</button>
index.js
const button = document.querySelector("button");
button.addEventListener("click", async () => {
const handle = await window.showSaveFilePicker();
const writable = await handle.createWritable();
await writable.write("BOOP");
await writable.close();
});

In the tweet below, I choose a file and then append either “boop” or “potato” to the file, depending on which button is clicked:

Demo & code

Try selecting a video, an image, or a text file with any of the three input methods in the demo underneath.

By looking at the file.type, the demo determines how it should read the file. If it’s a video or an image, it reads the contents as a data URL and sets the src for a <video> or <img> tag.

Anything else, and it reads and displays the plain text that’s inside that file.

You can view the code for this demo

input area

input type="file"

File System Access API

Dropzone

output area

Name

Size

type

lastModified

content