Статьи

Загрузитесь с Файловым API

Почему загрузка аватаров ограничивает нас по размеру файла? Вы знаете: «Пожалуйста, выберите изображение (максимум 50 КБ)». И почему веб-приложения для манипулирования фотографиями не были реализованы, так как canvas уже давно существует?

Ответ на оба эти вопроса сводится к производительности. До сих пор нам нужен медленный плагин или маршрут через сервер для создания и изменения файлов в браузере. Но для пользователей Internet Explorer 10, Firefox и Chrome разработчики имеют в своем арсенале великолепный File API, позволяющий выполнять эти операции непосредственно в браузере.

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

Разрушение Файлового API

Файловый API ( как определено W3C ) — это не один тип, а набор типизированных объектов, функций и методов.

FileList

FileList — это типизированный объект, который существует во многих местах. Во-первых, как свойство элемента ввода формы, тип которого «файл». Во-вторых, как часть события, отправляемого в событие удаления файла или событие буфера обмена (в результате копирования и вставки).

Например, допустим, у вас есть элемент управления вводом формы, такой как этот:

 <input type = file onchange = "console.log (this.files.length)" множественный />

Всякий раз, когда пользователь щелкает по входу формы и выбирает файл, это действие отправляет обработчик события onchange, и у объекта FileList (расположенного в this.files относительно элемента input) есть новые записи. В этом примере количество выбранных пользователем файлов выводится на консоль браузера.

Хотя объект FileList ведет себя аналогично нативному массиву Array — в том смысле, что вы можете перебирать его содержимое, как вы можете с помощью массива, — FileList содержит только неизменяемые экземпляры объектов File (описано ниже).

файл

Объект File представляет собой отдельный файл из FileList. Объект File содержит хэш метаданных только для чтения о файле, включая имя, дату последнего изменения, размер и тип. Он также используется в качестве ссылки и может быть передан в FileReader для чтения его содержимого.

Blob (Большой двоичный объект)

Интерфейс Blob предоставляет необработанные двоичные данные файла. Удаленные данные могут быть использованы как объект BLOB-объекта через XHRRequest, если для xhr.responseType установлено значение «blob». Новый объект BLOB-объектов является экземпляром API-интерфейса Blob и включает собственные методы, такие как blob.slice (i, i + n), которые можно использовать для нарезки большого объекта BLOB-объекта на более мелкие объекты BLOB-объектов. (Я использую фразу «интерфейс Blob», когда речь идет о типе объекта JavaScript, и «объект BLOB» для обозначения одного экземпляра интерфейса Blob.)

Кроме того, с помощью конструктора интерфейса Blob крошечные объекты BLOB-объектов могут быть снова объединены вместе с этим интерфейсом.

  новый Blob ([blob, blob, ...]) 

Вы найдете пример в демонстрационной версии BlobBuilder , которая загружает один файл MP3, а затем разбивает его на разные аудиофайлы (дорожки), каждый из которых применяется к собственному тегу <audio> для воспроизведения. В демоверсии пользователь может реконструировать MP3 в другом порядке, объединяя дорожки, и даже загружать новый объект BLOB-объекта в формате MP3.

Примечание: W3C устарел BlobBuilder в пользу Blob. Оба дают одинаковый результат, но по-разному. Кроме того, BlobBuilder от WebKit варьируется в зависимости от Internet Explorer и Firefox, поэтому лучше сначала определить функции для Blob.

FileReader

Интерфейс FileReader принимает объект File или Blob и считывает его содержимое. Объекты File или Blob являются просто ссылками на что-то, хранящееся на локальном компьютере. FileReader может быть оптимизирован для чтения файлов различных типов — например, Text (UTF-8), ArrayBuffer (для двоичного файла) или base64 data-uri. В следующем примере показано, как получить текст файла с помощью FileReader, используя экземпляр объекта BLOB-объекта.

  var reader = new FileReader ();
	 reader.onload = function (e) {
	 console.log (e.target.result);
	 }
	 reader.readAsText (блоб); 

Точно так же вы можете читать другой контент, используя метод read, который лучше всего подходит для типа файла: readAsArrayBuffer (отлично подходит для работы с большими двоичными файлами) или readAsDataURL (отлично, если вы хотите быстро внедрить контент в объект DOM, например изображение или аудиофайл).

FileReader включает в себя несколько прослушивателей событий: onerror, onloadstart, onabort и onprogress (что полезно для создания индикатора выполнения для больших файлов и для обнаружения проблем).

Схемы URI

Схемы URI — это URI, представляющие объекты в документе. Ресурс может быть файлом или большим двоичным объектом, и соответствующий ему URL-адрес известен как URL-адрес объекта. Имея ссылку Blob или File, вы можете создать URL объекта с помощью createObjectURL. Например:

  var objecturl = window.URL.createObjectURL (blob) 

возвращает URL, который ссылается на объект ресурса, что-то вроде «blob: http% 3A // test.com / 666e6730-f45c-47c1-8012-ccc706f17191».

Эта строка может быть размещена в любом месте, где может быть размещен типичный URI, например, в src тега изображения, если URI объекта представляет собой изображение. Ресурс объекта длится столько же, сколько и документ, поэтому обновите страницу, и его больше нет.

FileSaver

Интерфейс FileSaver предоставляет методы для записи больших двоичных объектов в каталог загрузок пользователя. Реализация довольно аккуратная и простая:

  window.saveAs (blob, "filename") 

Однако ни один из браузеров в настоящее время не имеет этого. Только Internet Explorer 10 поддерживает простую альтернативу: navigator.msSaveOrOpenBlob, а также navigator.msSaveBlob . Но, как вы увидите, есть прокладки, которые могут создать аналогичную функциональность, хотя и без изящного пользовательского опыта.

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

Присоединяйтесь к партии и создайте пример

Я решил одеться для вечеринки ( рис. 1 ) и создать свой собственный маленький инструмент для манипуляции с фотографиями на http://adodson.com/graffiti . Проверьте это. Выберите изображение в вашей файловой системе, нарисуйте над экраном, а затем загрузите свой шедевр — и все это с помощью файловых API (плюс несколько методов canvas и события указателя).

фигура 1
Рисунок 1. Приложение API Graffiti File

Реализация приложения граффити

Даже с яркой картинкой это приложение имеет довольно простую предпосылку:

  • Выберите изображение в качестве фона, используя File + FileList и FileReader.
  • Загрузите изображение в тег canvas и управляйте изображением с помощью HTML5 Canvas API.
  • Загрузите новое изображение с помощью Blob (или BlobBuilder), saveAs (или saveBlob) или взломайте тег привязки с помощью URL-адресов объектов.

Шаг 1: Выбор файла

Пользователь может выбрать изображение для этого приложения несколькими способами.

  • Выберите файл, используя форму ввода
  • Перетащите файл
  • Скопируйте и вставьте файл из буфера обмена

Каждый из этих подходов подключает своих слушателей к запуску пользовательской функции с именем readFile (), которая использует экземпляр FileReader для извлечения данных файла и добавления изображения пользователя на холст. Вот код функции.

  // readFile, загружает объекты File (которые также являются изображениями) в наш Canvas
	 // @param File Object
	 function readFile (file) {
	 // Создать новый объект FileReader
	 var reader = new FileReader ();
	 // Установить обработчик загрузки, потому что мы загружаем файлы в него асинхронно
	 reader.onload = function (e) {
	 // Ответ содержит Data-Uri, который мы можем затем загрузить на холст
	 applyDataUrlToCanvas (reader.result);
	 };
	 reader.reaAsDataURL (файл);
	 } 

Здесь readFile берет ссылку на файл (показано ниже) и создает новый экземпляр объекта FileReader. Эта функция считывает данные как DataURL, но она также может считывать данные как двоичные или даже как ArrayBuffer.

Чтобы подключить функцию, вы используете ссылку на файл в качестве параметра, используя один из подходов, упомянутых ранее.

Выберите файл с помощью формы ввода

FileAPI (увы) в настоящее время не определяет собственный метод для запуска выбора файла. Но старый ввод доверенной формы с type = file делает работу довольно хорошо:

  <input type = "file" name = "picture" accept = "image / png, image / jpeg" /> 

Столь же неловко, как и элемент ввода формы, он имеет некоторые новые дополнительные атрибуты, которые идеально подходят для этого приложения.

Атрибут accept указывает, какие типы файлов являются приемлемыми. В этом примере это PNG и JPEG. Устройство оставлено для того, чтобы обрабатывать это соответствующим образом (например, Windows по умолчанию открывает пользовательскую библиотеку изображений и показывает только файлы этих типов).

Множественный тег позволяет пользователю выбрать один или несколько файлов за один шаг.

Затем вам нужно привязать прослушиватели событий к событию изменения ввода формы, чтобы выбор пользователя автоматически вызывал readFile:

  document.querySelector ('input [name = picture]'). onchange = function (e) {
	 ReadFile (e.target.files [0]);
	 } 

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

Введите немного стиля

Показанное здесь поле ввода формы не совсем соответствует моей эстетике или, возможно, вашей. Поэтому поместите его абсолютно над другим выбранным вами элементом с непрозрачностью ноль (но будьте готовы к тому, что он будет иметь фиксированную ширину, которая может мешать другим элементам). Или немного сложнее: отправьте пользовательское событие щелчка на поле ввода, расположенное за пределами экрана. Узнайте больше об этой дискуссии здесь .

Перетащите файлы

Файлы можно перетаскивать из проводника и использовать модель событий, аналогичную хаку ввода формы. Событие «drop» происходит, когда пользователь выпускает изображение поверх холста. Событие содержит свойство с именем dataTransfer, которое имеет дочернее свойство с именем files. В следующем коде e.dataTransfer.files является экземпляром FileList (как упоминалось ранее, FileList содержит список ссылок на File), а первый элемент File является параметром для readFile. Это поддерживается в Webkit, Firefox и Internet Explorer 10. Вот пример:

  // остановить FireFox от замены всей страницы файлом.
	 canvas.ondragover = function () {return false;  };
	 // Добавить обработчик отбрасывания
	 canvas.ondrop = function (e) {
	 e.preventDefault ();  е = е ||  window.event;
	 var files = e.dataTransfer.files;
	 если (файлы) {
	 ReadFile (файлы [0]);
	 }
	 }; 

Скопируйте и вставьте данные файла

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

В приведенном ниже коде данные буфера обмена просматриваются, и отфильтровываются записи, соответствующие свойствам типа и вида, соответственно «* / image» и «file». Экземпляр File элемента получается с помощью getAsFile (), который передается в readFile.

  // вставить данные буфера обмена
	 // Ну, не все это только изображения.
	 document.onpaste = function (e) {
	 e.preventDefault ();
	 если (e.clipboardData && e.clipboardData.items) {
	 // вставленное изображение
	 для (var i = 0, items = e.clipboardData.items; i <items.length; i ++) {
	 if (items [i] .kind === 'file' && items [i] .type.match (/ ^ image /)) {
	 ReadFile (пункты [I] .getAsFile ());
	 перемена;
	 }
	 }
	 }
	 вернуть ложь;
	 }; 

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

Шаг 2: Загрузите изображение на холст

Функция readFile на шаге 1 передает созданный URL-адрес данных другой пользовательской функции applyDataUrlToCanvas. Эта функция рисует выбранное изображение на холсте. Вот как это работает:

  • Найдите ширину и высоту изображения.
  • Найдите ориентацию изображения.
  • Нарисуйте наилучший вариант изображения на холсте.

Найти ширину и высоту

Используя функцию DOM Image, вы можете легко определить размеры любого изображения. Это удобная техника, которая выглядит примерно так:

  var img = новое изображение ();
	 img.onload = function () {
	 // img.width
	 // img.height
	 }
	 img.src = dataURL; 

Найти ориентацию

К сожалению, есть проблема с шириной и высотой, и она большая: снимок мог быть сделан в портретном режиме и сохранен в альбомной ориентации, или изображение могло быть перевернуто вверх дном.

Некоторые камеры вместо сохранения изображения в правильной ориентации предоставляют свойство Ориентация в данных EXIF изображения. Это можно прочитать из двоичных данных изображения.

Преобразование URL-адреса данных в двоичную строку легко:

  var base64 = dataUrl.replace (/^.*?,/, '');
	 var binary = atob (base64); 

И, к счастью, есть клиентская библиотека с открытым исходным кодом EXIF , написанная Джейкобом Сейделином, которая будет возвращать данные EXIF ​​как объект. Да, классно!

  <script src = "http://www.nihilogic.dk/labs/exif/exif.js"> </ script>
	 <script src = "http://www.nihilogic.dk/labs/binaryajax/binaryajax.js"> </ script>
	 <Скрипт>
	 var exif = EXIF.readFromBinaryFile (новый BinaryFile (двоичный));
	 //exif.Orientation
	 </ Скрипт> 

Свойство Orientation представляет собой целое число в диапазоне 1–8, соответствующее четырем поворотам и четырем переворотам (довольно избыточным).

Нарисуйте изображение на холсте

Обнаружив значение Orientation, вы можете вращать и рисовать на холсте. Если вы хотите увидеть мой алгоритм, просто покопайтесь в исходном коде, который вы можете найти по адресу http://adodson.com/graffiti/ и https://github.com/MrSwitch/graffiti .

Шаг 3: Загрузите изображение

Последний шаг — загрузить измененное изображение. В репертуаре FileAPI вы можете использовать интерфейс Blob для создания файлов в клиенте. Оберните это новым msSaveBlob в Internet Explorer 10 или хаком атрибута загрузки (готовится) в других современных браузерах, и вместе они позволяют вам скачивать файлы в клиенте.

Попробуйте сами в демоверсии . Нажмите на кнопку Скачать.

Демонстрация использует метод canvas.toBlob в Internet Explorer 10 для получения ссылки на файл изображения, которое в данный момент отрисовывается в теге canvas. Для Chrome и FireFox отлично работает прокладка toBlob .

  canvas.toBlob (function (file) {
	 // Создать файл BLOB-объекта,
	
	 // затем загружаем с помощью FileSaver
	 } 

Сделайте копию экземпляра Blob Object

Мы должны быть в состоянии пропустить этот шаг, но из-за причуд во всех браузерах нельзя использовать API FileSave непосредственно из экземпляра Blob, возвращаемого canvas.toBlob. Вы должны скопировать это.

Интерфейс BlobBuilder, используемый для создания новых ссылок Blob, поддерживается в Internet Explorer 10, Chrome и FireFox. Но этот интерфейс уже был заменен конструктором Blob, который сейчас имеет ограниченную поддержку.

Во-первых, удалите префиксы поставщиков BlobBuilder:

  // Shim BlobBuilder с префиксами поставщика
	 window.BlobBuilder ||  (window.BlobBuilder = window.MSBlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder); 

Затем проверьте ваш код на будущее и протестируйте конструктор Blob. В противном случае создайте BlobBuilder для создания BLOB-объекта . (Лучше всего обернуть методы в try-catch.) В текущем браузере Chrome для Android есть ошибка. Вот код

  вар блоб;
	 if («Blob» в окне) {
	 пытаться{
	 // Новый интерфейс Blob
	 blob = new Blob ([file], {"type": "image / png"});
	 поймать (е) {}
	 }
	 если (! блоб) {
	 пытаться{
	 // Устаревший интерфейс BlobBuilder
	 var bb = new BlobBuilder ();
	 bb.append (файл);
	 blob = bb.getBlob ("image / png");
	 }
	 поймать (е) {}
	 } 

Загрузка BLOB-объекта с помощью FileSaver

FileSaver API также является стандартом, который еще не принят ни одним из существующих браузеров. Тем не менее, в случае Internet Explorer 10 вы можете использовать функцию msSaveBlob (что потрясающе), а для Chrome и FireFox вы можете по крайней мере использовать их в будущем с префиксами поставщиков. Итак, функция saveAs нуждается в некотором массовом отбрасывании:

  window.saveAs ||  (window.saveAs == window.navigator.msSaveBlob || window.webkitSaveAs || window.mozSaveAs || window.msSaveAs / ** || URL Download Hack ** /); 

Этот запасной вариант (полностью описанный на https://gist.github.com/3552985 ) упрощает интерфейс FileSaver, используя URL-адрес объекта для нашего нового изображения. Для браузеров, которые поддерживают атрибут загрузки в теге привязки, shim определяет href как URL объекта, а затем отправляет событие click, чтобы заставить его загрузить или иным образом открыть в новой вкладке. О, какие спутанные ткани мы плетем.

Наконец, с учетом большого двоичного объекта и имени файла метод saveAs инициирует загрузку:

  var name = 'Graffiti.png';
	 если () {window.saveAs
	 // Перемещаем содержимое объекта компоновщика в BLOB и
	 window.saveAs (blob, name);
	 }
	 еще {
	 // Fallover, открыть как DataURL
	 window.open (canvas.toDataURL ());
	 } 

Здесь, если прокладка saveAs несовместима, фолловер откроет новую вкладку с Base64 Data-URL . Это работает в Internet Explorer 9, но Internet Explorer 8 ограничен длиной DataURI 32 КБ.

Завершение

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

Если вам требуется поддержка старых браузеров, взгляните на мой shim dropfile.js для Internet Explorer, который упрощает FileReader и создает base-URI- код для base64, а также Downloadify для замены shim на основе Flash на FileSaver.

Ресурсы

Эта статья была первоначально опубликована по адресу http://msdn.microsoft.com/en-us/magazine/jj835793.aspx и воспроизводится здесь с разрешения.