HTML5 Files API - чтение файлов

60

Возможность веб-хранилища поддерживается в HTML5 на хорошем уровне. Но это не единственный способ получения информации веб-страницами. В эту область понемногу вводится несколько других стандартов, направленных на выполнение разных типов задач хранения данных. Одним из таких стандартов является File API, который технически не входит в HTML5, но имеет хороший уровень поддержки на всех современных браузерах.

Судя по расплывчатому названию этого стандарта, может показаться, что это всеохватывающий стандарт для чтения и записи файлов на жесткий диск клиента. Но этот стандарт не настолько амбициозный или мощный. Он просто разрешает посетителю веб-сайта выбрать файл на своем жестком диске и передать его непосредственно коду JavaScript, исполняющемуся на просматриваемой веб-странице. Код может открыть этот файл и работать с его данными, будто это простой текст или что-то более сложное. Здесь ключевым аспектом является то обстоятельство, что файл передается непосредственно коду JavaScript. В отличие от обычной выгрузки файла, он никогда не отправляется на веб-сервер.

Также важно знать, что File API не может делать. Самое важное, что он не может изменять файлы или создавать новые файлы. Чтобы сохранить какие-либо данные, нужно прибегать к другому механизму, например данные можно отправить на веб-сервер посредством запроса XMLHttpRequest или же поместить их в локальное хранилище.

Судя по этому, можно подумать, что интерфейс File API менее полезен, чем локальное хранилище, и для большинства веб-сайтов это будет правильный вывод. Но этот стандарт приоткрывает дверь в область, в которую HTML раньше не входил, по крайней мере без помощи модулей расширений.

В настоящее время интерфейс File API является необходимой функциональностью для определенных типов специализированных приложений, но в будущем его возможности могут быть расширены, и важность его значительно возрастет. Например, будущие версии интерфейса могут позволять веб-страницам сохранять файлы на жесткий диск клиента при условии, что пользователь контролирует имя файла и место его сохранения, используя диалоговое окно "Сохранить как". Модули расширения, наподобие Flash, уже оснащены такой способностью.

Поддержка браузерами интерфейса File API

Интерфейс File API не имеет такой широкой поддержки, как веб-хранилище. Текущая браузерная поддержка этого интерфейса приводится в таблице ниже:

Поддержка браузерами интерфейса File API
Браузер IE Firefox Chrome Safari Opera Safari iOS Android
Минимальная версия 10 3.6 8 6 11.1 - 3

Эти браузеры почти наверняка не реализуют все возможности File API, т.к. некоторые части стандарта (для работы с большими объемами двоичных данных и "вырезания" порций данных) все еще находятся в процессе разработки.

Получение файла

Прежде чем интерфейс File API сможет что-либо сделать с файлом, ему нужно этот файл получить. Эту задачу можно выполнить тремя разными способами, но все они одинаковые в одном ключевом аспекте — веб-страница может получить файл только в том случае, если посетитель явно выберет и предоставит его веб-странице.

Способы получения файла следующие:

Посредством элемента <input>

Присвоив атрибуту type значение file, мы получим стандартное окно для закачивания файла. Но с помощью небольшого сценария JavaScript и File API этот файл можно открыть локально.

Посредством скрытого элемента <input>

Элемент <input> очень непривлекательный. Чтобы не обезображивать им свою страницу, его можно скрыть и создать более прилично выглядящую кнопку. Нажатие этой кнопки активирует JavaScript-код, вызывающий метод click() скрытого элемента <input>, который открывает стандартное диалоговое окно выбора файла.

Посредством метода drag and drop

Если браузер поддерживает этот метод, файл можно перетащить с рабочего стола или окна браузера и отпустить его в определенной области веб-страницы.

В последующих разделах мы рассмотрим все эти подходы более подробно.

Чтение текстового файла

Самой простой операцией File API будет чтение содержимого простого текстового файла. На рисунке ниже показана веб-страница, которая с помощью этого интерфейса считывает разметку веб-страницы, а потом выводит этот текст в окне браузера:

Загрузка текстового файла посредством File API

Создание этого примера начинается с элемента <input type="file">, который создает текстовое поле и кнопку "Выберите файл":

<input id="fileInput" type="file" size="50" onchange="processFiles(this.files)">
<div id="fileOutput"></div>

Но в то время как элемент <input> обычно вставляется в контейнер <form>, чтобы файл можно было закачать на веб-сервер, в данном случае наш элемент <input> играет самостоятельную роль. Когда посетитель страницы выбирает файл, активируется событие onchange элемента <input>, что в свою очередь активирует функцию processFiles(). Как раз на этом этапе и открывается файл посредством самого обыкновенного кода JavaScript.

Теперь рассмотрим по частям функцию processFiles(). Сперва нам нужно взять первый файл из коллекции файлов, предоставленных элементом <input>. Если явно не разрешить выбор нескольких файлов (посредством атрибута multiple), коллекция файлов будет гарантированно содержать только один файл, размещенный в элементе 0 массива файлов:

function processFiles(files) {
    var file = files[0];
    ...
    
}

Все объекты файлов обладают тремя потенциально полезными свойствами. Свойство name сообщает нам имя файла (без пути), свойство size указывает размер файла в байтах, а свойство type информирует о MIME-типе файла, если его можно определить. Эти свойства можно считывать и использовать их в дополнительной логике, например, отказаться обрабатывать файлы больше определенного размера или разрешить обрабатывать файлы только определенного типа.

Далее создается объект FileReader для обработки файла:

// ...
var reader = new FileReader();

Теперь мы почти готовы вызвать один из методов объекта FileReader, чтобы извлечь содержимое файла. Но эти методы являются асинхронными. Это означает, что они начинают чтение файла, но не ожидают получения данных. Поэтому, чтобы получить данные, сначала нужно обработать событие onload:

reader.onload = function (e) {
    // Когда это событие активируется, данные готовы.
    // Вставляем их в страницу в элемент <div>
    var output = document.getElementById("fileOutput");   
    output.textContent = e.target.result;
};

Наконец, подготовив этот обработчик события, можно вызывать метод readAsText() объекта FileReader:

reader.readAsText(file);

Этот метод сбрасывает все содержимое файла в одну длинную строку, вставляемую в свойство e.target.result, которое в свою очередь отправляется событию onload.

Метод readAsText() работает должным образом только для текстового содержимого файла, но не для двоичного. Это означает, что он идеально подходит для работы с файлами HTML, как показано на рисунке выше.

Кроме метода readAsText(), объект FileReader имеет еще несколько других методов для чтения файлов: readAsBinaryString(), readAsDataURL() и readAsArrayBuffer(), но последний метод не поддерживается в Firefox.

Метод readAsBinary() предоставляет веб-приложению возможность считывать двоичные данные, хотя он вставляет эти данные в текстовую строку несколько неуклюже, что не является особенно эффективным. А если вы еще захотите разобраться с этими данными, то для этого вам придется мучиться с исключительно запутанным кодом.

Метод readAsDataURL() предоставляет легкий способ захватывать данные изображения. Мы рассмотрим применение этого метода далее, но сначала мы выясним, как сделать нашу страницу более красивой.

Замена элемента <input>

Веб-разработчики сходятся во мнении: стандартный элемент управления <input>, применяемый для выгрузки файлов, выглядит далеко не лучшим образом. Но хотя нам не избежать использования его, совсем необязательно, чтобы пользователи его видели. Мы можем просто скрыть его с помощью следующего стилевого правила:

#fileInput {
	display:none;
}

Теперь нам нужно добавить новый элемент управления, который будет инициировать процесс предоставления файла. Для этого достаточно обычной кнопки со ссылкой, которую мы можем разукрасить каким угодно образом:

<button onclick="showFileInput()">Добавить файл</button>

Последним шагом будет обработка нажатия кнопки путем инициализации вручную элемента <input> через вызов метода click() этого элемента:

function showFileInput() {
    var fileInput = document.getElementById("fileInput");
    fileInput.click();
}

Теперь нажатие этой кнопки запускает функцию showFileInput(), которая "нажимает" скрытую кнопку "Выберите файл" и отображает диалоговое окно для выбора файла. Это, в свою очередь, активирует событие onchange скрытого элемента <input>, которое запускает функцию processFiles() точно таким же образом, как и раньше.

Чтение файла изображения

Как мы узнали, объект FileReader обрабатывает текстовое содержимое в один простой прием. Благодаря методу readAsDataURL() он с такой же легкостью обрабатывает и изображения.

На рисунке ниже показан пример, для реализации которого используются две новые возможности - поддержка изображений и выбор файла методом drag and drop:

Вставка изображения с помощью перетаскивания

На этой странице изображение можно представить двумя способами: выбрать файл изображения с помощью элементов управления внизу или перетащить и отпустить нужный файл в выделенную пунктиром рамку.

Выбранное изображение используется в качестве фона элемента, хотя его с тем же успехом можно было вставить в холст и обрабатывать попиксельно в холсте. Этот метод можно применить, например, чтобы создать страницу, на которую посетитель может вставить изображение, доступное для редактирования, а затем сохранить конечный результат посредством вызова объекта XMLHttpRequest.

При создании такой страницы сначала нужно решить, какой элемент будет получать перетаскиваемые файлы. В нашем примере это <div>-элемент dropBox:

  <div id="dropBox">
     <div>Перетащите изображение сюда...</div>
  </div>
  <input id="fileInput" type="file" onchange="processFiles(this.files)">
  <img id="thumbnail">

С помощью правил таблицы стилей задаем полю для перетаскивания файла желаемый размер и оформляем рамкой и фоном:

#dropBox {
    margin: 15px;
    width: 300px;
    height: 300px;
    border: 5px dashed gray;
    border-radius: 8px;
    background: lightyellow;
    background-size: 100%;
    background-repeat: no-repeat;
    text-align: center;
}
 
#dropBox div {
    margin: 100px 70px;
    color: orange;
    font-size: 25px;
    font-family: Verdana, Arial, sans-serif;
}

Возможно, вы заметили, что в поле для перетаскивания файла изображения установлены свойства background-size и background-repeat для подготовки к следующей операции. Когда файл изображения перетаскивается в поле <div>, то изображение используется в качестве фона этого элемента. Свойство background-size обеспечивает уменьшение размеров изображения, чтобы его можно было видеть полностью. А значение no-repeat свойства background-repeat обеспечивает, что изображение не повторяется для заполнения оставшегося пространства.

Для обработки перетаскивания и отпускания файла нам требуются три события: ondragenter, ondragover и ondrop. При загрузке страницы ко всем этим событиям подключается соответствующий обработчик:

var dropBox;

window.onload = function() {
  dropBox = document.getElementById("dropBox");
  dropBox.ondragenter = ignoreDrag;
  dropBox.ondragover = ignoreDrag;
  dropBox.ondrop = drop;
}

Функция ignoreDrag() обрабатывает как событие ondragenter (которое инициализируется, когда указатель мыши с перетаскиваемым файлом входит в зону сбрасывания), так и событие ondragover (которое срабатывает постоянно во время движения курсора мыши в зоне сбрасывания). Такой подход возможен потому, что нам никак не нужно реагировать на эти действия, кроме как сообщить браузеру не предпринимать никаких действий. Код функции выглядит следующим образом:

function ignoreDrag(e) {
  // Обеспечиваем, чтобы никто другой не получил это событие, 
  // т.к. мы выполняем операцию перетаскивания
  e.stopPropagation();
  e.preventDefault();
}

Событие ondrop более важное, т.к. в нем мы получаем файл и обрабатываем его. Но поскольку файл для страницы можно предоставить двумя способами, собственно для выполнения работы функция drop() вызывает функцию processFiles():

function drop(e) {
  // Аннулируем это событие для всех других
  e.stopPropagation();
  e.preventDefault();
 
  // Получаем перемещенные файлы
  var data = e.dataTransfer;
  var files = data.files;
	 
  // Передаем полученный файл функции для обработки файлов
  processFiles(files);
}

Функция processFiles() является последним этапом в процессе перетаскивания файла. Она создает объект FileReader, подключает функцию обработки к событию onload и вызывает метод readAsDataURL() для преобразования данных изображения в данные URL:

function processFiles(files) {
  var file = files[0];
  
  var reader = new FileReader();

  reader.onload = function (e) {
    // Используем URL изображения для заполнения фона
	dropBox.style.backgroundImage = "url('" + e.target.result + "')";
  };
  
  // Начинаем считывать изображение
  reader.readAsDataURL(file);
}

Объект FileReader имеет еще несколько других событий, которые можно использовать при чтении файлов изображений. Событие onprogress срабатывает периодически в процессе длинных операций, чтобы предоставить информацию об объеме загруженных данных на текущий момент. (Операцию можно аннулировать до ее завершения, вызвав метод abort() объекта FileReader.) Событие onerror срабатывает в случае проблем с открытием или чтением файла. А событие onloadend — при завершении операции любым способом, включая ее преждевременное завершение вследствие ошибки.

Пройди тесты
Лучший чат для C# программистов