Использование файлов из веб-приложений
Примечание: Эта функция доступна в Потоках обработки веб-задач.
Используя API файлов, веб-контент может попросить пользователя выбрать локальные файлы и затем прочитать содержимое этих файлов. Этот выбор можно выполнить, используя элемент HTML <input type="file"> или перетаскиванием.
Доступ к выбранным файлам
Рассмотрим такой HTML:
<input type="file" id="input" multiple />
API файлов позволяет получить доступ к FileList, содержащему File-объекты, представляющие файлы, выбранные пользователем.
Атрибут multiple элемента input позволяет пользователю выбрать несколько файлов.
Доступ к первому выбранному файлу с помощью классического селектора DOM:
const selectedFile = document.getElementById("input").files[0];
Доступ к выбранным файлам при событии изменения
Также можно (но необязательно) получить доступ к FileList через событие change. Вам необходимо использовать EventTarget.addEventListener() для добавления обработчика события change, как показано ниже:
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, предоставляемый DOM, перечисляет все файлы, выбранные пользователем, каждый из которых задан как объект File. Вы можете определить количество выбранных файлов, проверив значение атрибута списка файлов length.
const numFiles = fileList.length;
Индивидуальные объекты File можно получить, обратившись к списку как к массиву.
Объект File предоставляет три атрибута, содержащие полезную информацию о файле.
Пример: отображение размера файла(ов)
Следующий пример демонстрирует возможное использование свойства size.
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<title>File(s) size</title>
</head>
<body>
<form name="uploadForm">
<div>
<input id="uploadInput" type="file" multiple />
<label for="fileNum">Selected files:</label>
<output id="fileNum">0</output>;
<label for="fileSize">Total size:</label>
<output id="fileSize">0</output>
</div>
<div><input type="submit" value="Send file" /></div>
</form>
<script>
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,
);
</script>
</body>
</html>
Использование скрытых элементов ввода файла с помощью метода click()
Вы можете скрыть элемент ввода файла <input> и представить свой собственный интерфейс для открытия выбора файла и отображения выбранного файла или файлов. Для этого стилизуйте элемент ввода с помощью display:none и вызовите метод click() для элемента <input>.
Рассмотрим такой HTML:
<input type="file" id="fileElem" multiple accept="image/*" style="display:none" /> <button id="fileSelect" type="button">Select some files</button>
Код, обрабатывающий событие click, может выглядеть следующим образом:
const fileSelect = document.getElementById("fileSelect");
const fileElem = document.getElementById("fileElem");
fileSelect.addEventListener(
"click",
(e) => {
if (fileElem) {
fileElem.click();
}
},
false,
);
Вы можете стилизовать элемент <button> по своему желанию.
Использование элемента label для запуска скрытого элемента ввода файла
Чтобы открыть диалог выбора файла без использования JavaScript (метода click()), можно использовать элемент <label>. Обратите внимание, что в этом случае элемент ввода не должен быть скрыт с помощью display: none (ни visibility: hidden), в противном случае метка не будет доступна с клавиатуры. Используйте вместо этого технику скрытия контента.
Рассмотрим такой HTML:
<input type="file" id="fileElem" multiple accept="image/*" class="visually-hidden" /> <label for="fileElem">Select some files</label>
и такой 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;
}
Нет необходимости добавлять JavaScript-код для вызова fileElem.click(). Также в этом случае вы можете стилизовать элемент метки по своему усмотрению. Вам необходимо предоставить визуальную подсказку для состояния фокуса скрытого поля ввода на его метке, будь то обводка, как показано выше, или цвет фона или тень. (На момент написания Firefox не отображает эту визуальную подсказку для элементов <input type="file">.)
Выбор файлов с помощью перетаскивания
Вы также можете позволить пользователю перетаскивать файлы в ваше веб-приложение.
Первым шагом является создание зоны перетаскивания. Точная часть вашего содержимого, которая будет принимать перетаскивание, может варьироваться в зависимости от дизайна вашего приложения, но создание элемента, принимающего события перетаскивания, очень просто:
let dropbox;
dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
В этом примере мы превращаем элемент с идентификатором dropbox в нашу зону перетаскивания. Это делается путем добавления обработчиков для событий dragenter, dragover и drop.
В нашем случае нам на самом деле не нужно делать ничего с событиями dragenter и dragover, поэтому эти функции простые. Они просто останавливают распространение события и предотвращают выполнение стандартного действия:
function dragenter(e) {
e.stopPropagation();
e.preventDefault();
}
function dragover(e) {
e.stopPropagation();
e.preventDefault();
}
Настоящая магия происходит в функции drop():
function drop(e) {
e.stopPropagation();
e.preventDefault();
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
Здесь мы извлекаем поле dataTransfer из события, извлекаем из него список файлов и передаём его в handleFiles(). С этого момента обработка файлов одинакова, независимо от того, использовал ли пользователь элемент input или перетаскивание.
Пример: отображение миниатюр выбранных пользователем изображений
Предположим, вы разрабатываете следующий отличный сайт для обмена фотографиями и хотите использовать HTML для отображения предварительных миниатюр изображений перед фактической загрузкой пользователем. Вы можете создать элемент ввода или зону перетаскивания, как обсуждалось ранее, и вызвать функцию, такую как функция handleFiles() ниже.
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 = (e) => {
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
}
Здесь наш цикл, обрабатывающий выбранные пользователем файлы, проверяет атрибут type каждого файла, чтобы увидеть, начинается ли его MIME-тип с image/. Для каждого файла, который является изображением, мы создаем новый элемент img. CSS может использоваться для установки красивых границ или теней и для указания размера изображения, поэтому это делать здесь не нужно.
Каждое изображение имеет добавленный класс CSS obj, что облегчает его поиск в дереве DOM. Мы также добавляем атрибут file к каждому изображению, указывающий File для изображения; это позволит нам позже извлечь изображения для фактической загрузки. Мы используем Node.appendChild() для добавления новой миниатюры в область предварительного просмотра нашего документа.
Затем мы устанавливаем FileReader для асинхронной загрузки изображения и прикрепления его к элементу img. После создания нового объекта FileReader мы настраиваем его функцию onload и вызываем readAsDataURL() для запуска операции чтения в фоновом режиме. Когда всё содержимое файла изображения загружено, оно преобразуется в URL data:, который передаётся в обратный вызов onload. Наша реализация этой процедуры устанавливает атрибут img элемента src на загруженное изображение, что приводит к появлению изображения на миниатюре на экране пользователя.
Использование URL-адресов объектов
Методы DOM URL.createObjectURL() и URL.revokeObjectURL() позволяют создавать простые строки URL, которые могут быть использованы для ссылки на любые данные, которые могут быть обращены по ссылке с помощью объекта DOM File, включая локальные файлы на компьютере пользователя.
Когда у вас есть объект File, на который вы хотите сослаться по URL из HTML, вы можете создать для него URL-адрес объекта следующим образом:
const objectURL = window.URL.createObjectURL(fileObj);
URL объекта — это строка, идентифицирующая объект File. Каждый вызов URL.createObjectURL() создаёт уникальный URL-адрес объекта, даже если вы уже создали URL-адрес объекта для этого файла. Каждый из них должен быть освобождён. Хотя они освобождаются автоматически при разгрузке документа, если ваша страница динамически использует их, вы должны явно освободить их, вызвав URL.revokeObjectURL():
URL.revokeObjectURL(objectURL);
Пример использования URL-адресов объектов для отображения изображений
В этом примере используются объектные URL для отображения миниатюр изображений. Кроме того, он отображает другую информацию о файлах, включая их имена и размеры.
Вот как выглядит HTML, представляющий интерфейс:
<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>
Это задаёт наш элемент файла <input>, а также ссылку, вызывающую файловый выборщик (так как мы скрываем поле ввода файла, чтобы избежать отображения не слишком привлекательного пользовательского интерфейса). Это объясняется в разделе Использование скрытых элементов ввода файла с помощью метода click(), а также метод, вызывающий файловый выборщик.
Следующий метод handleFiles():
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 (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 = () => {
URL.revokeObjectURL(img.src);
};
li.appendChild(img);
const info = document.createElement("span");
info.textContent = `${this.files[i].name}: ${this.files[i].size} bytes`;
li.appendChild(info);
}
}
}
Это начинается с извлечения URL-адреса <div> с идентификатором fileList. Это блок, в который мы вставим наш список файлов, включая миниатюры.
Если объект FileList, переданный в handleFiles() равен null, мы устанавливаем внутренний HTML блока, чтобы отобразить "Файлы не выбраны!". В противном случае мы начинаем создание нашего списка файлов следующим образом:
-
Создаётся новый элемент неупорядоченного списка (
<ul>). -
Новый элемент списка вставляется в блок
<div>с помощью методаNode.appendChild(). -
Для каждого
FileвFileList, представленномfiles, выполняется следующее:- Создаётся новый элемент элемента списка (
<li>) и вставляется в список. - Создаётся новый элемент изображения (
<img>). - Устанавливается исходное изображение объектного URL, представляющего файл, используя
URL.createObjectURL()для создания URL блоба. - Устанавливается высота изображения в 60 пикселей.
- Настраивается обработчик события загрузки изображения для освобождения объектного URL, поскольку он больше не нужен, после того как изображение загружено. Это делается вызовом метода
URL.revokeObjectURL()и передачей в качестве параметра строки объектного URL, как указано вimg.src. - Добавляется новый элемент списка в список.
- Создаётся новый элемент элемента списка (
Вот живая демонстрация приведенного выше кода:
Пример: Загрузка выбранного пользователем файла
В этом примере показано, как позволить пользователю загрузить файлы (например, изображения, выбранные в предыдущем примере) на сервер.
Примечание: Обычно предпочтительно выполнять HTTP-запросы с помощью API Fetch вместо XMLHttpRequest. Однако в данном случае мы хотим показать пользователю процесс загрузки, и эта функция пока не поддерживается API Fetch, поэтому в примере используется XMLHttpRequest.
Работа по стандартизации уведомлений о прогрессе с использованием API Fetch находится в https://github.com/whatwg/fetch/issues/607.
Создание задач загрузки
Продолжая код, создающий миниатюры в предыдущем примере, помните, что каждое изображение миниатюры находится в классе CSS obj с соответствующим File, прикреплённым в атрибуте file. Это позволяет нам выбрать все изображения, выбранные пользователем для загрузки, с помощью Document.querySelectorAll(), например:
function sendFiles() {
const imgs = document.querySelectorAll(".obj");
for (let i = 0; i < imgs.length; i++) {
new FileUpload(imgs[i], imgs[i].file);
}
}
document.querySelectorAll извлекает NodeList всех элементов в документе с классом CSS obj. В нашем случае это будут все миниатюры изображений. После получения этого списка, тривиально пройти по нему и создать новый экземпляр FileUpload для каждого. Каждый из них обрабатывает загрузку соответствующего файла.
Обработка процесса загрузки файла
Функция FileUpload принимает два входных параметра: элемент изображения и файл, из которого следует прочитать данные изображения.
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",
(e) => {
if (e.lengthComputable) {
const percentage = Math.round((e.loaded * 100) / e.total);
self.ctrl.update(percentage);
}
},
false,
);
xhr.upload.addEventListener(
"load",
(e) => {
self.ctrl.update(100);
const canvas = self.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;
}
Функция FileUpload() показанная выше, создаёт индикатор загрузки, который используется для отображения информации о прогрессе, и затем создаёт XMLHttpRequest для обработки загрузки данных.
Перед фактической передачей данных выполняется несколько подготовительных шагов:
- Для
XMLHttpRequestустанавливается слушатель события загрузкиprogress, чтобы обновлять индикатор загрузки новой информацией о процентах, таким образом, по мере загрузки индикатор загрузки будет обновляться на основе последней информации. - Для
XMLHttpRequestустанавливается обработчик события загрузкиload, чтобы обновить информацию о прогрессе индикатора загрузки до 100%, чтобы гарантировать, что индикатор прогресса действительно достигнет 100% (в случае особенностей гранулярности во время процесса). Затем он удаляет индикатор загрузки, поскольку он больше не нужен. Это вызывает исчезновение индикатора загрузки после завершения загрузки. - Запрос на загрузку файла изображения открывается вызовом метода
XMLHttpRequestopen()для начала создания POST-запроса. - Тип MIME для загрузки устанавливается вызовом функции
XMLHttpRequestoverrideMimeType(). В данном случае используется универсальный тип MIME; вам может или не потребоваться устанавливать тип MIME в зависимости от вашего случая использования. - Объект
FileReaderиспользуется для преобразования файла в двоичную строку. - Наконец, когда содержимое загружается, вызывается функция
XMLHttpRequestsend()для загрузки содержимого файла.
Асинхронная обработка процесса загрузки файла
В этом примере, использующем PHP на стороне сервера и JavaScript на стороне клиента, демонстрируется асинхронная загрузка файла.
<?php
if (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 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 = () => {
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 id="dropzone" style="margin:30px; width:500px; height:300px; border:1px dotted grey;">Drag & drop your file here</div>
</div>
</body>
</html>
Пример: Использование объектных URL для отображения PDF
Объектные URL можно использовать для других задач, помимо просто изображений! Они могут использоваться для отображения встраиваемых файлов PDF или любых других ресурсов, которые может отобразить браузер.
В Firefox, чтобы PDF отображался в iframe (вместо предложения о загрузке файла), предпочтение pdfjs.disabled должно быть установлено в значение false.
<iframe id="viewer"></iframe>
И вот изменение атрибута src:
const obj_url = URL.createObjectURL(blob);
const iframe = document.getElementById("viewer");
iframe.setAttribute("src", obj_url);
URL.revokeObjectURL(obj_url);
Пример: Использование объектных URL с другими типами файлов
Вы можете манипулировать файлами других форматов аналогичным образом. Вот как предварительно просмотреть загруженное видео:
const video = document.getElementById("video");
const obj_url = URL.createObjectURL(blob);
video.src = obj_url;
video.play();
URL.revokeObjectURL(obj_url);
См. также
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/File_API/Using_files_from_web_applications