Using the File API, which was added to the DOM in HTML5, it's now possible for web content to 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.
If you want to use the DOM File API from extensions or other browser chrome code, you can; however, note there are some additional features to be aware of. See 在 Chrome 代码中使用 DOM 文件 API 了解细节。
Consider this HTML:
<input type="file" id="input" multiple>
The File API makes it possible to access a
FileList
包含
File
objects representing the files selected by the user.
multiple
属性在
input
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];
It is also possible (but not mandatory) to access the
FileList
透过
change
event. You need to use
EventTarget.addEventListener()
to add the
change
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 */
}
FileList
object provided by the DOM lists all of the files selected by the user, each specified as a
File
object. You can determine how many files the user selected by checking the value of the file list's
length
属性:
const numFiles = fileList.length;
Individual
File
objects can be retrieved by simply accessing the list as an array:
for (let i = 0, numFiles = fileList.length; i < numFiles; i++) {
const file = fileList[i];
// ...
}
This loop iterates over all the files in the file list.
There are three attributes provided by the
File
object that contain useful information about the file.
名称
The file's name as a read-only string. This is just the file name, and does not include any path information.
size
The size of the file in bytes as a read-only 64-bit integer.
type
""
if the type couldn't be determined.
The following example shows a possible use of the
size
特性:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>File(s) size</title>
</head>
<body>
<form name="uploadForm">
<div>
<input id="uploadInput" type="file" name="myFiles" multiple>
selected files: <span id="fileNum">0</span>;
total size: <span id="fileSize">0</span>
</div>
<div><input type="submit" value="Send file"></div>
</form>
<script>
function updateSize() {
let nBytes = 0,
oFiles = this.files,
nFiles = oFiles.length;
for (let nFileId = 0; nFileId < nFiles; nFileId++) {
nBytes += oFiles[nFileId].size;
}
let sOutput = nBytes + " bytes";
// optional code for multiples approximation
const aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
sOutput = nApprox.toFixed(3) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)";
}
// end of optional code
document.getElementById("fileNum").innerHTML = nFiles;
document.getElementById("fileSize").innerHTML = sOutput;
}
document.getElementById("uploadInput").addEventListener("change", updateSize, false);
</script>
</body>
</html>
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 with
display:none
and calling the
click()
method on the
<input>
元素。
Consider this HTML:
<input type="file" id="fileElem" multiple accept="image/*" style="display:none"> <button id="fileSelect">Select some files</button>
The code that handles the
click
event can look like this:
const fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem");
fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
}, false);
You can style the new button for opening the file picker as you wish.
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 using
display: none
(nor
visibility: hidden
), otherwise the label would not be keyboard-accessible. Use the
visually-hidden technique
代替。
Consider this HTML:
<input type="file" id="fileElem" multiple accept="image/*" class="visually-hidden"> <label for="fileElem">Select some files</label>
and this CSS:
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
/* Separate rule for compatibility, :focus-within is required on modern Firefox and Chrome */
input.visually-hidden:focus + label {
outline: thin dotted;
}
input.visually-hidden:focus-within + label {
outline: thin dotted;
}
There is no need to add JavaScript code to call
fileElem.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.)
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 ID
dropbox
into our drop zone. This is done by adding listeners for the
dragenter
,
dragover
,和
drop
事件。
We don't actually need to do anything with the
dragenter
and
dragover
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 the
drop()
函数:
function drop(e) {
e.stopPropagation();
e.preventDefault();
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
Here, we retrieve the
dataTransfer
field from the event, pull the file list out of it, and then pass that to
handleFiles()
. From this point on, handling the files is the same whether the user used the
input
element or drag and drop.
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 the
handleFiles()
function below.
function handleFiles(files) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
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 = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
reader.readAsDataURL(file);
}
}
Here our loop handling the user-selected files looks at each file's
type
attribute to see if its MIME type begins with the string "
image/
"). For each file that is an image, we create a new
img
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 class
obj
added to it, making it easy to find in the DOM tree. We also add a
file
attribute to each image specifying the
File
for the image; this will let us fetch the images for actual upload later. We use
Node.appendChild()
to add the new thumbnail to the preview area of our document.
Next, we establish the
FileReader
to handle asynchronously loading the image and attaching it to the
img
element. After creating the new
FileReader
object, we set up its
onload
function and then call
readAsDataURL()
to start the read operation in the background. When the entire contents of the image file are loaded, they are converted into a
data:
URL which is passed to the
onload
callback. Our implementation of this routine sets the
img
元素的
src
attribute to the loaded image which results in the image appearing in the thumbnail on the user's screen.
DOM
URL.createObjectURL()
and
URL.revokeObjectURL()
methods let you create simple URL strings that can be used to reference any data that can be referred to using a DOM
File
object, including local files on the user's computer.
When you have a
File
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 the
File
object. Each time you call
URL.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 calling
URL.revokeObjectURL()
:
URL.revokeObjectURL(objectURL);
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" id="fileElem" multiple accept="image/*" style="display:none"> <a href="#" id="fileSelect">Select some files</a> <div id="fileList"> <p>No files selected!</p> </div>
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 section
Using hidden file input elements using the click() method
, as is the method that invokes the file picker.
handleFiles()
method follows:
const fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem"),
fileList = document.getElementById("fileList");
fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
}, false);
fileElem.addEventListener("change", handleFiles, false);
function handleFiles() {
if (!this.files.length) {
fileList.innerHTML = "<p>No files selected!</p>";
} else {
fileList.innerHTML = "";
const list = document.createElement("ul");
fileList.appendChild(list);
for (let i = 0; i < this.files.length; i++) {
const li = document.createElement("li");
list.appendChild(li);
const img = document.createElement("img");
img.src = URL.createObjectURL(this.files[i]);
img.height = 60;
img.onload = function() {
URL.revokeObjectURL(this.src);
}
li.appendChild(img);
const info = document.createElement("span");
info.innerHTML = this.files[i].name + ": " + this.files[i].size + " bytes";
li.appendChild(info);
}
}
}
This starts by fetching the URL of the
<div>
with the ID
fileList
. This is the block into which we'll insert our file list, including thumbnails.
若
FileList
object passed to
handleFiles()
is
null
, we simply set the inner HTML of the block to display "No files selected!". Otherwise, we start building our file list, as follows:
<ul>
) element is created.
<div>
block by calling its
Node.appendChild()
方法。
File
在
FileList
represented by
文件
:
<li>
) element and insert it into the list.
<img>
) 元素。
URL.createObjectURL()
to create the blob URL.
URL.revokeObjectURL()
method and passing in the object URL string as specified by
img.src
.
Here is a live demo of the code above:
Another thing you might want to do is let the user upload the selected file or files (such as the images selected using the previous example) to a server. This can be done asynchronously very easily.
Continuing with the code that built the thumbnails in the previous example, recall that every thumbnail image is in the CSS class
obj
with the corresponding
File
attached in a
file
attribute. This allows us to select all of the images the user has chosen for uploading using
Document.querySelectorAll()
,像这样:
function sendFiles() {
const imgs = document.querySelectorAll(".obj");
for (let i = 0; i < imgs.length; i++) {
new FileUpload(imgs[i], imgs[i].file);
}
}
Line 2 fetches a
NodeList
, called
imgs
, of all the elements in the document with the CSS class
obj
. 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 new
FileUpload
instance for each. Each of these handles uploading the corresponding file.
FileUpload
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;
const self = this;
this.xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
const percentage = Math.round((e.loaded * 100) / e.total);
self.ctrl.update(percentage);
}
}, false);
xhr.upload.addEventListener("load", function(e){
self.ctrl.update(100);
const canvas = self.ctrl.ctx.canvas;
canvas.parentNode.removeChild(canvas);
}, false);
xhr.open("POST", "http://demos.hacks.mozilla.org/paul/demos/resources/webservices/devnull.php");
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
reader.onload = function(evt) {
xhr.send(evt.target.result);
};
reader.readAsBinaryString(file);
}
FileUpload()
function shown above creates a throbber, which is used to display progress information, and then creates an
XMLHttpRequest
to handle uploading the data.
Before actually transferring the data, several preparatory steps are taken:
XMLHttpRequest
's upload
progress
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.
XMLHttpRequest
's upload
load
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.
XMLHttpRequest
's
open()
method to start generating a POST request.
XMLHttpRequest
function
overrideMimeType()
. 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.
FileReader
object is used to convert the file to a binary string.
XMLHttpRequest
function
send()
is called to upload the file's content.
This example, which uses PHP on the server side and JavaScript on the client side, demonstrates asynchronous uploading of a file.
<?php
if (isset($_FILES['myFile'])) {
// Example:
move_uploaded_file($_FILES['myFile']['tmp_name'], "uploads/" . $_FILES['myFile']['name']);
exit;
}
?><!DOCTYPE html>
<html>
<head>
<title>dnd binary upload</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="application/javascript">
function sendFile(file) {
const uri = "/index.php";
const xhr = new XMLHttpRequest();
const fd = new FormData();
xhr.open("POST", uri, true);
xhr.onreadystatechange = function() {
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 = function() {
const dropzone = document.getElementById("dropzone");
dropzone.ondragover = dropzone.ondragenter = function(event) {
event.stopPropagation();
event.preventDefault();
}
dropzone.ondrop = function(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 id="dropzone" style="margin:30px; width:500px; height:300px; border:1px dotted grey;">Drag & drop your file here...</div>
</div>
</body>
</html>
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 preference
pdfjs.disabled
must be set to
false
.
<iframe id="viewer">
And here is the change of the
src
属性:
const obj_url = URL.createObjectURL(blob);
const iframe = document.getElementById('viewer');
iframe.setAttribute('src', obj_url);
URL.revokeObjectURL(obj_url);
You can manipulate files of other formats the same way. Here is how to preview uploaded video:
const video = document.getElementById('video');
const obj_url = URL.createObjectURL(blob);
video.src = obj_url;
video.play();
URL.revokeObjectURL(obj_url);
| 规范 | 状态 | 注释 |
|---|---|---|
|
HTML 实时标准
The definition of 'File upload state' in that specification. |
实时标准 | |
| 文件 API | 工作草案 | 初始定义 |
File