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.
<input type="file" />
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.
<div>Welcome to THE DROP ZONE</div>
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);});
const dropZoneEl = document.querySelector("div");dropZoneEl.addEventListener("dragover", (event) => {event.preventDefault();});dropZoneEl.addEventListener("drop", (event) => {event.preventDefault();console.log("Something was dropped");if (event.dataTransfer.items[0].kind === "file") {const file = event.dataTransfer.items[0].getAsFile();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
.
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! CallcreateImageBitmap(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 acreateObjectURL
method that accepts aBlob
!
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.
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.
<button>choose a file to write to</button>
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