FileAPI: Загрузка и обработка файлов
2 
Что было
3 
Что было
4 
Что было 
Flash
5 
Что было 
HTML/JS
6 
Требования 
• Множественный выбор файлов 
• Получение информации (название, размер, тип) 
• Создание пред-просмотра на клиенте 
• Масштабирование, кадрирование и поворот 
• Загрузка на сервер + CORS
7 
Требования 
• Не зависеть от вёрстки 
• Никакой бизнес-логики 
• Расширяемость 
• Самодостаточность
FileAPI 2.0
Error #2038 
10%
Error #2038 
5%
11 
Поддержка 
• Chrome 10+ 
• FireFox 3.6+ 
• Opera 11.1+ 
• Safari 5.4+
12
13 
ПОЛУЧЕНИЕ СПИСКА 
ФАЙЛОВ
14 
HTML5 
<input id="file" type="file" multiple /> 
<script> 
var input = document.getByElementId("file"); 
input.addEventListener("change", function (){ 
var files = input.files; 
}, false); 
</script>
15 
HTML5 
<input id="file" type="file" multiple /> 
<script> 
var input = document.getByElementId("file"); 
input.addEventListener("change", function (){ 
var files = input.files; 
}, false); 
</script>
16 
HTML5 
<input id="file" type="file" multiple /> 
<script> 
var input = document.getByElementId("file"); 
input.addEventListener("change", function (){ 
var files = input.files; 
}, false); 
</script>
17 
Flash 
FLASH
18 
Flash 
FLASH
19 
Flash 
FLASH 
Flash --> jsFunc([{ 
id: "346515436346", // уникальный идентификатор 
name: "hello-world.png", // название файла 
type: "image/png", // mime-type 
size: 43325 // рамер 
}, { 
// etc. 
}])
Взаимодействие 
flash.cmd("imageTransform", { 
id: "346515436346", // идентификатор файла 
matrix: { }, // "матрица" трансформации 
callback: "__UNIQ_NAME__" // размер 
});
21 
API 
<span class="js-fileapi-wrapper" style="position: relative"> 
<input id="file" type="file" multiple /> 
</span> 
<script> 
var input = document.getByElementId("file"); 
FileAPI.event.on(input, "change", function (){ 
var files = FileAPI.getFiles(input); 
}); 
</script>
22 
API 
<span class="js-fileapi-wrapper" style="position: relative"> 
<input id="file" type="file" multiple /> 
</span> 
<script> 
var input = document.getByElementId("file"); 
FileAPI.event.on(input, "change", function (evt){ 
var files = FileAPI.getFiles(evt); 
}); 
</script>
23 
ФИЛЬТРАЦИЯ
FileReader 
• readAsDataURL(file) 
• readAsArrayBuffer(file) 
• readAsText(file[, encoding])
25 
Фильтрация 
FileAPI.filterFiles(files, function (file, info){ 
return file.size < 10 * FileAPI.MB; 
}, function (files, ignore){ 
if( files.length > 0 ){ 
// ... 
} 
});
Информация о файле 
FileAPI.getInfo(audioFile, function (err, tags){ 
if( !err ){ 
var li = document.createElement("li"); 
li.innerHTML = tags.artist +" – "+ tags.title; 
ul.appendChild(li); 
} 
});
ПРЕДПРОСМОТР
Предпросмотр 
DataURI
Предпросмотр 
DataURI 
Base64
Предпросмотр 
DataURI 
Base64 
<img/> 
“data:image/png;base64,” + Base64
Предпросмотр 
DataURI 
Base64 
<img/> 
“data:image/png;base64,” + Base64
Предпросмотр 
HTML5 
• FileReader.readAsDataURL(file) — позволяет 
прочесть содержимое файла как DataURL 
• URL.createObjectURL(file) — создает ссылку, 
указывающую на файл
Предпросмотр 
HTML5 
• FileReader.readAsDataURL(file) — позволяет 
прочесть содержимое файла как DataURL 
• URL.createObjectURL(file) — создает ссылку, 
указывающую на файл 
• URL.revokeObjectURL(file) — убрать ссылку
34 
FileAPI.Image 
• crop(x, y, width, height) — кадрирование 
• resize(width[, height]) — масштабирование 
• rotate(deg) — поворот 
• preview(width, height) — кадрирует и масштабирует 
• get(callback) — получить итоговое изображение
35 
Matrix 
{ // параметры фрагмента оригинала 
sx: Number, 
sy: Number, 
sw: Number, 
sh: Number, 
// требуемые размеры 
dw: Number, 
dh: Number, 
deg: Number 
}
36 
FileAPI.Image 
FileAPI.Image(imageFle) 
.crop(300, 300) 
.resize(100, 100) 
.get(function (err, img){ 
if( !err ){ 
images.appendChild(img); 
} 
}) 
;
Сжатие 
5197х4987
Сжатие
Сжатие 
5197х4987 2598х2493
Сжатие x 2 
5197х4987 2598х2493 1299х1246
Сжатие x 5 
5197х4987 2598х2493 1299х1246 
100х100
Сжатие 
Серия
ЗАГРУЗКА ФАЙЛОВ
Загрузка 
<form 
action="/upload" 
method="post" 
enctype="multipart/form-data"> 
<input name="files" type="file" /> 
<input name="foo" value="bar" type="hidden" /> 
</form>
Загрузка 
<form 
target="__UNIQ__" 
action="/upload" 
method="post" 
enctype="multipart/form-data"> 
<iframe name="__UNIQ__"></iframe> 
<input name="files" type="file" /> 
<input name="foo" value="bar" type="hidden" /> 
</form> 
Уникальный идентификатор
Загрузка 
XMLHttpRequest level 2 
FormData
Загрузка 
// собираем данные для отправки 
var form = new FormData 
form.append("foo", "bar"); 
form.append("attach", file); 
// отправояем на сервер 
var xhr = new XMLHttpRequest; 
xhr.open("POST", "/upload", true); 
xhr.send(form)
Загрузка 
// собираем данные для отправки 
var form = new FormData 
form.append("foo", "bar"); 
form.append("attach", file); 
// отправояем на сервер 
var xhr = new XMLHttpRequest; 
xhr.open("POST", "/upload", true); 
xhr.send(form)
Загрузка 
canvasToBlob(canvas, function (blob){ 
// собираем данные для отправки 
var form = new FormData 
form.append("foo", "bar"); 
form.append("attach", blob, "filename.png"); 
// отправляем на сервер 
var xhr = new XMLHttpRequest; 
xhr.open("POST", "/upload", true); 
xhr.send(form) 
});
Загрузка 
dataURL = canvas.toDataURL(“image/png”); 
<cavnas/> DataURL
Загрузка 
dataURL = canvas.toDataURL(“image/png”); 
base64 = dataURL.replace(/^data:[^,]+,/, “”); 
<cavnas/> DataURL Base64
Загрузка 
dataURL = canvas.toDataURL(“image/png”); 
base64 = dataURL.replace(/^data:[^,]+,/, “”); 
binaryString = window.atob(base64); 
<cavnas/> DataURL Base64 
BinaryString
53 
Multipart 
var uniq = '1234567890'; 
var xhr = new XMLHttpRequest; 
xhr.open('POST', '/upload', true); 
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+uniq); 
xhr.sendAsBinary([ 
'--_'+ uniq 
, 'Content-Disposition: form-data; name="my-file"; filename="hello-world.png"' 
, 'Content-Type: image/png' 
, '' 
, binaryString 
, '--_'+ uniq +'--' 
].join('rn'));
54 
Загрузка 
var xhr = FileAPI.upload({ 
url: '/upload', 
data: { foo: 'bar' }, 
headers: { 'Session-Id': '...' }, 
files: { images: imageFiles, others: otherFiles }, 
imageTransform: { maxWidth: 1024, maxHeight: 768 }, 
upload: function (xhr){}, 
progress: function (event, file){}, 
complete: function (err, xhr, file){}, 
fileupload: function (file, xhr){}, 
fileprogress: function (event, file){}, 
filecomplete: function (err, xhr, file){} 
});
55 
Загрузка 
var xhr = FileAPI.upload({ 
url: '/upload', 
data: { foo: 'bar' }, 
headers: { 'Session-Id': '...' }, 
files: { images: imageFiles, others: otherFiles }, 
imageTransform: { maxWidth: 1024, maxHeight: 768 }, 
upload: function (xhr){}, 
progress: function (event, file){}, 
complete: function (err, xhr, file){}, 
fileupload: function (file, xhr){}, 
fileprogress: function (event, file){}, 
filecomplete: function (err, xhr, file){} 
});
Загрузка 
imageTransform: 
{ 
huge: { maxWidth: 800, maxHeight: 600, rotate: 90 }, 
medium: { width: 320, height: 240, preview: true }, 
small: { width: 100, height: 120, preview: true } 
}
57 
XHR 
var xhr = FileAPI.upload({ … });
58 
XHR 
var xhr = FileAPI.upload({ … }); 
• status — HTTP status code 
• statusText — HTTP status text 
• responseText — ответ сервера 
• getResponseHeader(name) — получить заголовок ответа сервера 
• getAllResponseHeaders() — получить все заголовки 
• abort() — отменить загрузку
Drag’n’Drop 
<div class="dropzone"></div> 
Перетащите файлы сюда
Drag’n’Drop 
<div class="dropzone dropzone_hover"></div> 
4
Drag’n’Drop 
<div id="el" class="dropzone"></div> 
<script> 
var el = document.getElementById("el"); 
FileAPI.event.dnd(el, function (over){ 
if( ever ){ 
el.classList.add("dropzone_hover"); 
} else { 
el.classList.remove("dropzone_hover"); 
} 
}, function (files){ 
uploadFiles(files); 
}); 
</script> 
4
Drag’n’Drop 
<div id="el" class="dropzone"></div> 
<script> 
var el = document.getElementById("el"); 
FileAPI.event.dnd(el, function (over){ 
if( ever ){ 
el.classList.add("dropzone_hover"); 
} else { 
el.classList.remove("dropzone_hover"); 
} 
}, function (files){ 
uploadFiles(files); 
}); 
</script> 
4
Drag’n’Drop 
<div id="el" class="dropzone"></div> 
<script> 
var el = document.getElementById("el"); 
FileAPI.event.dnd(el, function (over){ 
if( ever ){ 
el.classList.add("dropzone_hover"); 
} else { 
el.classList.remove("dropzone_hover"); 
} 
}, function (files){ 
uploadFiles(files); 
}); 
</script> 
4
Drag’n’Drop 
<div id="el" class="dropzone"></div> 
<script> 
var el = document.getElementById("el"); 
FileAPI.event.dnd(el, function (over){ 
if( ever ){ 
el.classList.add("dropzone_hover"); 
} else { 
el.classList.remove("dropzone_hover"); 
} 
}, function (files){ 
uploadFiles(files); 
}); 
</script> 
4
Drag’n’Drop 
function uploadFiles(dropFiles){ 
FileAPI.upload({ 
url: "/upload", 
files: { attaches: dropFiles }, 
complete: function (err, xhr){ 
if( !err ){ 
// файлы загружены 
} 
} 
}); 
} 
4
Спустя год 
• Закрыто более 100 github issue 
• GruntJS (JSLint, QUnit + PhantomJS, Uglify) 
• Улучшена работа с изображениями 
• Добавлена поддержка работы с веб-камерой 
• Разработан jQuery plugin для типовых задач 
• Улучшена документация (+ примеры)
OVERLAYS
Overlay 
FileAPI.Image(imageFile) 
.overlay([{ 
x: 10 
, y: 10 
, src: "/i/watermark.png" 
, rel: FileAPI.Image.RITGHT_BOTTOM 
, opacity: 0.85 
]) 
.get(function (err/**String*/, img/**HTMLElement*/)({ /*__*/ }) 
;
Overlay 
var xhr = FileAPI.upload({ 
url: "...", 
files: images, 
imageTransform: { 
overlay: { /* options */ } 
} 
});
FILTERS
Filters 
FileAPI.Image(imageFile) 
.filter(function (canvas, doneFn){ 
// processing... 
doneFn(); 
}) 
.get(function (err/**String*/, img/**HTMLElement*/)({ 
/*__*/ 
}) 
;
CamanJS
Filters + CamanJS 
FileAPI.Image(imageFile) 
.filter("hazyDays") 
.get(function (err/**String*/, 
img/**HTMLElement*/)({ 
/*__*/ 
}) 
;
WebCam
WebCam 
navigator.getUserMedia( constraints, success, error ); 
— запросить разрешение на использование 
микрофона и/или камеры
WebCam 
navigator.getUserMedia({ video: true }, function (stream){ 
var video = document.getElementById("webcam"); 
var streamSrc = URL.createObjectURL(stream); 
video.src = streamUrl; 
});
WebCam 
navigator.getUserMedia({ video: true }, function (stream){ 
var video = document.getElementById("webcam"); 
var streamSrc = URL.createObjectURL(stream); 
video.src = streamUrl; 
});
WebCam 
function onTakeShot(){ 
var video = document.getElementById("webcam"); 
var canvas = document.createElement("canvas"); 
var ctx = canvas.getContext("2d"); 
canvas.width = video.videoWidth; 
canvas.height = video.videoHeight; 
ctx.drawImage(video, 0, 0); 
}
FileAPI.Camera 
var el = document.getElementById("webcam"); 
FileAPI.Camera.publish(el, function (cam){ 
});
FileAPI.Camera 
var el = document.getElementById("webcam"); 
FileAPI.Camera.publish(el, function (cam){ 
FileAPI.event.on(btn, "click", function (){ 
var shot = cam.shot(); // FileAPI.Image instance 
FileAPI.upload({ url: "...", files: shot }); 
}); 
});
jQuery.FileAPI 
• «Одной кнопкой» — выбрать и автоматически загрузить файл 
• «Ограничения» — минимальный/максимальный размер как 
файла, так и изображения, по ширине и высоте 
• «Работа с очередью» — сортировка и фильтрация очереди 
загрузки файлов 
• «Изображения» — предпросмотр, поворот и кадрирование 
• «Интерфейс» — гибкая и прозрачная настройка интерфейса 
• А также Drag’n’Drop и WebCam
jQuery.FileAPI
jQuery.FileAPI
jQuery.FileAPI
jQuery.FileAPI
jQuery.FileAPI
https://github.com/mailru/FileAPI 
Константин Лебедев 
@ibnRubaXa 
k.lebedev@corp.mail.ru

More Related Content

PDF
FrontTalks: Константин Лебедев (Mail.ru), File API: обработка файлов на клиен...
PPTX
константин лебедев
PDF
Примеры решения типичных задач за рамками ядра Yii2
PDF
YiiConf: Миграции и инсталляции
PPTX
О безопасном использовании PHP wrappers
PDF
Продвинутое использование ActiveRecord в Yii2
PPTX
Vipolnenie komand na servere
PPT
Общая архитектура Yii2
FrontTalks: Константин Лебедев (Mail.ru), File API: обработка файлов на клиен...
константин лебедев
Примеры решения типичных задач за рамками ядра Yii2
YiiConf: Миграции и инсталляции
О безопасном использовании PHP wrappers
Продвинутое использование ActiveRecord в Yii2
Vipolnenie komand na servere
Общая архитектура Yii2

What's hot (20)

PDF
Школа-студия разработки для iOS. Лекция 4. Работа с данными
PDF
JS утиліти WordPress на практиці
PDF
Укрощение XML
PPTX
Игорь Любин - PowerShell - ConfeT&QA 2011
PDF
BlueEyes russian
PPT
CodeFest 2013. Никонов Г. — Как мы разрабатываем приложения для Windows Phone...
PDF
Роман Акинфеев «Разработка RESTful API with all bells and whistles»
PPT
Drupal 7 и history.js или как ajax инфицировать сайт
PPT
Drupal 7 and History.js
PPTX
Web осень 2013 лекция 5
PDF
Интуит. Разработка приложений для iOS. Лекция 8. Работа с данными
PDF
The Old New ASP.NET
PPT
LDAP in infrastructure (RootConf 2009)
PDF
Web осень 2013 лекция 3
PPTX
JDI: Автоматизировать проще, чем кажется
PDF
Михаил Давыдов — Транспорт, Ajax
PPTX
Разработка крупного Standalone проекта на юнити: улучшаем производительность
PDF
«Highload блоки и новое api к ним»
PDF
JavaScript Базовый. Занятие 07.
PDF
Хранение, обработка и отдача статики с использованием \Zend\File. Опыт социал...
Школа-студия разработки для iOS. Лекция 4. Работа с данными
JS утиліти WordPress на практиці
Укрощение XML
Игорь Любин - PowerShell - ConfeT&QA 2011
BlueEyes russian
CodeFest 2013. Никонов Г. — Как мы разрабатываем приложения для Windows Phone...
Роман Акинфеев «Разработка RESTful API with all bells and whistles»
Drupal 7 и history.js или как ajax инфицировать сайт
Drupal 7 and History.js
Web осень 2013 лекция 5
Интуит. Разработка приложений для iOS. Лекция 8. Работа с данными
The Old New ASP.NET
LDAP in infrastructure (RootConf 2009)
Web осень 2013 лекция 3
JDI: Автоматизировать проще, чем кажется
Михаил Давыдов — Транспорт, Ajax
Разработка крупного Standalone проекта на юнити: улучшаем производительность
«Highload блоки и новое api к ним»
JavaScript Базовый. Занятие 07.
Хранение, обработка и отдача статики с использованием \Zend\File. Опыт социал...
Ad

Similar to FileAPI 2.0 (20)

PPTX
course js day 4
PDF
Jsfwdays 2013-2
PDF
Yii: миграции и инсталляции
PPTX
Silverlight 4, есть ли жизнь на десктопе
PPT
Web осень 2012 лекция 4
PPT
Web весна 2013 лекция 4
PDF
Mihail davidov js-ajax
PPTX
Как не утонуть в мегабайтах JS-кода
PPTX
Sumin
PPTX
Взломать Web-сайт на ASP.NET? Сложно, но можно!
PPT
Web осень 2012 лекция 10
PPT
Web весна 2013 лекция 10
PDF
"Жизнь без интернета" Кувалдин Артём, Яндекс
PPTX
PDF
Кэширование данных с помощью Service Worker
PPT
Web весна 2012 лекция 4
PPT
Take more from Jquery
PDF
WebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
PDF
UWDC 2013, Yii2
PPT
Настройка Apache и PHP
course js day 4
Jsfwdays 2013-2
Yii: миграции и инсталляции
Silverlight 4, есть ли жизнь на десктопе
Web осень 2012 лекция 4
Web весна 2013 лекция 4
Mihail davidov js-ajax
Как не утонуть в мегабайтах JS-кода
Sumin
Взломать Web-сайт на ASP.NET? Сложно, но можно!
Web осень 2012 лекция 10
Web весна 2013 лекция 10
"Жизнь без интернета" Кувалдин Артём, Яндекс
Кэширование данных с помощью Service Worker
Web весна 2012 лекция 4
Take more from Jquery
WebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
UWDC 2013, Yii2
Настройка Apache и PHP
Ad

FileAPI 2.0

  • 1. FileAPI: Загрузка и обработка файлов
  • 6. 6 Требования • Множественный выбор файлов • Получение информации (название, размер, тип) • Создание пред-просмотра на клиенте • Масштабирование, кадрирование и поворот • Загрузка на сервер + CORS
  • 7. 7 Требования • Не зависеть от вёрстки • Никакой бизнес-логики • Расширяемость • Самодостаточность
  • 11. 11 Поддержка • Chrome 10+ • FireFox 3.6+ • Opera 11.1+ • Safari 5.4+
  • 12. 12
  • 14. 14 HTML5 <input id="file" type="file" multiple /> <script> var input = document.getByElementId("file"); input.addEventListener("change", function (){ var files = input.files; }, false); </script>
  • 15. 15 HTML5 <input id="file" type="file" multiple /> <script> var input = document.getByElementId("file"); input.addEventListener("change", function (){ var files = input.files; }, false); </script>
  • 16. 16 HTML5 <input id="file" type="file" multiple /> <script> var input = document.getByElementId("file"); input.addEventListener("change", function (){ var files = input.files; }, false); </script>
  • 19. 19 Flash FLASH Flash --> jsFunc([{ id: "346515436346", // уникальный идентификатор name: "hello-world.png", // название файла type: "image/png", // mime-type size: 43325 // рамер }, { // etc. }])
  • 20. Взаимодействие flash.cmd("imageTransform", { id: "346515436346", // идентификатор файла matrix: { }, // "матрица" трансформации callback: "__UNIQ_NAME__" // размер });
  • 21. 21 API <span class="js-fileapi-wrapper" style="position: relative"> <input id="file" type="file" multiple /> </span> <script> var input = document.getByElementId("file"); FileAPI.event.on(input, "change", function (){ var files = FileAPI.getFiles(input); }); </script>
  • 22. 22 API <span class="js-fileapi-wrapper" style="position: relative"> <input id="file" type="file" multiple /> </span> <script> var input = document.getByElementId("file"); FileAPI.event.on(input, "change", function (evt){ var files = FileAPI.getFiles(evt); }); </script>
  • 24. FileReader • readAsDataURL(file) • readAsArrayBuffer(file) • readAsText(file[, encoding])
  • 25. 25 Фильтрация FileAPI.filterFiles(files, function (file, info){ return file.size < 10 * FileAPI.MB; }, function (files, ignore){ if( files.length > 0 ){ // ... } });
  • 26. Информация о файле FileAPI.getInfo(audioFile, function (err, tags){ if( !err ){ var li = document.createElement("li"); li.innerHTML = tags.artist +" – "+ tags.title; ul.appendChild(li); } });
  • 30. Предпросмотр DataURI Base64 <img/> “data:image/png;base64,” + Base64
  • 31. Предпросмотр DataURI Base64 <img/> “data:image/png;base64,” + Base64
  • 32. Предпросмотр HTML5 • FileReader.readAsDataURL(file) — позволяет прочесть содержимое файла как DataURL • URL.createObjectURL(file) — создает ссылку, указывающую на файл
  • 33. Предпросмотр HTML5 • FileReader.readAsDataURL(file) — позволяет прочесть содержимое файла как DataURL • URL.createObjectURL(file) — создает ссылку, указывающую на файл • URL.revokeObjectURL(file) — убрать ссылку
  • 34. 34 FileAPI.Image • crop(x, y, width, height) — кадрирование • resize(width[, height]) — масштабирование • rotate(deg) — поворот • preview(width, height) — кадрирует и масштабирует • get(callback) — получить итоговое изображение
  • 35. 35 Matrix { // параметры фрагмента оригинала sx: Number, sy: Number, sw: Number, sh: Number, // требуемые размеры dw: Number, dh: Number, deg: Number }
  • 36. 36 FileAPI.Image FileAPI.Image(imageFle) .crop(300, 300) .resize(100, 100) .get(function (err, img){ if( !err ){ images.appendChild(img); } }) ;
  • 40. Сжатие x 2 5197х4987 2598х2493 1299х1246
  • 41. Сжатие x 5 5197х4987 2598х2493 1299х1246 100х100
  • 44. Загрузка <form action="/upload" method="post" enctype="multipart/form-data"> <input name="files" type="file" /> <input name="foo" value="bar" type="hidden" /> </form>
  • 45. Загрузка <form target="__UNIQ__" action="/upload" method="post" enctype="multipart/form-data"> <iframe name="__UNIQ__"></iframe> <input name="files" type="file" /> <input name="foo" value="bar" type="hidden" /> </form> Уникальный идентификатор
  • 47. Загрузка // собираем данные для отправки var form = new FormData form.append("foo", "bar"); form.append("attach", file); // отправояем на сервер var xhr = new XMLHttpRequest; xhr.open("POST", "/upload", true); xhr.send(form)
  • 48. Загрузка // собираем данные для отправки var form = new FormData form.append("foo", "bar"); form.append("attach", file); // отправояем на сервер var xhr = new XMLHttpRequest; xhr.open("POST", "/upload", true); xhr.send(form)
  • 49. Загрузка canvasToBlob(canvas, function (blob){ // собираем данные для отправки var form = new FormData form.append("foo", "bar"); form.append("attach", blob, "filename.png"); // отправляем на сервер var xhr = new XMLHttpRequest; xhr.open("POST", "/upload", true); xhr.send(form) });
  • 50. Загрузка dataURL = canvas.toDataURL(“image/png”); <cavnas/> DataURL
  • 51. Загрузка dataURL = canvas.toDataURL(“image/png”); base64 = dataURL.replace(/^data:[^,]+,/, “”); <cavnas/> DataURL Base64
  • 52. Загрузка dataURL = canvas.toDataURL(“image/png”); base64 = dataURL.replace(/^data:[^,]+,/, “”); binaryString = window.atob(base64); <cavnas/> DataURL Base64 BinaryString
  • 53. 53 Multipart var uniq = '1234567890'; var xhr = new XMLHttpRequest; xhr.open('POST', '/upload', true); xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+uniq); xhr.sendAsBinary([ '--_'+ uniq , 'Content-Disposition: form-data; name="my-file"; filename="hello-world.png"' , 'Content-Type: image/png' , '' , binaryString , '--_'+ uniq +'--' ].join('rn'));
  • 54. 54 Загрузка var xhr = FileAPI.upload({ url: '/upload', data: { foo: 'bar' }, headers: { 'Session-Id': '...' }, files: { images: imageFiles, others: otherFiles }, imageTransform: { maxWidth: 1024, maxHeight: 768 }, upload: function (xhr){}, progress: function (event, file){}, complete: function (err, xhr, file){}, fileupload: function (file, xhr){}, fileprogress: function (event, file){}, filecomplete: function (err, xhr, file){} });
  • 55. 55 Загрузка var xhr = FileAPI.upload({ url: '/upload', data: { foo: 'bar' }, headers: { 'Session-Id': '...' }, files: { images: imageFiles, others: otherFiles }, imageTransform: { maxWidth: 1024, maxHeight: 768 }, upload: function (xhr){}, progress: function (event, file){}, complete: function (err, xhr, file){}, fileupload: function (file, xhr){}, fileprogress: function (event, file){}, filecomplete: function (err, xhr, file){} });
  • 56. Загрузка imageTransform: { huge: { maxWidth: 800, maxHeight: 600, rotate: 90 }, medium: { width: 320, height: 240, preview: true }, small: { width: 100, height: 120, preview: true } }
  • 57. 57 XHR var xhr = FileAPI.upload({ … });
  • 58. 58 XHR var xhr = FileAPI.upload({ … }); • status — HTTP status code • statusText — HTTP status text • responseText — ответ сервера • getResponseHeader(name) — получить заголовок ответа сервера • getAllResponseHeaders() — получить все заголовки • abort() — отменить загрузку
  • 59. Drag’n’Drop <div class="dropzone"></div> Перетащите файлы сюда
  • 60. Drag’n’Drop <div class="dropzone dropzone_hover"></div> 4
  • 61. Drag’n’Drop <div id="el" class="dropzone"></div> <script> var el = document.getElementById("el"); FileAPI.event.dnd(el, function (over){ if( ever ){ el.classList.add("dropzone_hover"); } else { el.classList.remove("dropzone_hover"); } }, function (files){ uploadFiles(files); }); </script> 4
  • 62. Drag’n’Drop <div id="el" class="dropzone"></div> <script> var el = document.getElementById("el"); FileAPI.event.dnd(el, function (over){ if( ever ){ el.classList.add("dropzone_hover"); } else { el.classList.remove("dropzone_hover"); } }, function (files){ uploadFiles(files); }); </script> 4
  • 63. Drag’n’Drop <div id="el" class="dropzone"></div> <script> var el = document.getElementById("el"); FileAPI.event.dnd(el, function (over){ if( ever ){ el.classList.add("dropzone_hover"); } else { el.classList.remove("dropzone_hover"); } }, function (files){ uploadFiles(files); }); </script> 4
  • 64. Drag’n’Drop <div id="el" class="dropzone"></div> <script> var el = document.getElementById("el"); FileAPI.event.dnd(el, function (over){ if( ever ){ el.classList.add("dropzone_hover"); } else { el.classList.remove("dropzone_hover"); } }, function (files){ uploadFiles(files); }); </script> 4
  • 65. Drag’n’Drop function uploadFiles(dropFiles){ FileAPI.upload({ url: "/upload", files: { attaches: dropFiles }, complete: function (err, xhr){ if( !err ){ // файлы загружены } } }); } 4
  • 66. Спустя год • Закрыто более 100 github issue • GruntJS (JSLint, QUnit + PhantomJS, Uglify) • Улучшена работа с изображениями • Добавлена поддержка работы с веб-камерой • Разработан jQuery plugin для типовых задач • Улучшена документация (+ примеры)
  • 68. Overlay FileAPI.Image(imageFile) .overlay([{ x: 10 , y: 10 , src: "/i/watermark.png" , rel: FileAPI.Image.RITGHT_BOTTOM , opacity: 0.85 ]) .get(function (err/**String*/, img/**HTMLElement*/)({ /*__*/ }) ;
  • 69. Overlay var xhr = FileAPI.upload({ url: "...", files: images, imageTransform: { overlay: { /* options */ } } });
  • 71. Filters FileAPI.Image(imageFile) .filter(function (canvas, doneFn){ // processing... doneFn(); }) .get(function (err/**String*/, img/**HTMLElement*/)({ /*__*/ }) ;
  • 73. Filters + CamanJS FileAPI.Image(imageFile) .filter("hazyDays") .get(function (err/**String*/, img/**HTMLElement*/)({ /*__*/ }) ;
  • 75. WebCam navigator.getUserMedia( constraints, success, error ); — запросить разрешение на использование микрофона и/или камеры
  • 76. WebCam navigator.getUserMedia({ video: true }, function (stream){ var video = document.getElementById("webcam"); var streamSrc = URL.createObjectURL(stream); video.src = streamUrl; });
  • 77. WebCam navigator.getUserMedia({ video: true }, function (stream){ var video = document.getElementById("webcam"); var streamSrc = URL.createObjectURL(stream); video.src = streamUrl; });
  • 78. WebCam function onTakeShot(){ var video = document.getElementById("webcam"); var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0); }
  • 79. FileAPI.Camera var el = document.getElementById("webcam"); FileAPI.Camera.publish(el, function (cam){ });
  • 80. FileAPI.Camera var el = document.getElementById("webcam"); FileAPI.Camera.publish(el, function (cam){ FileAPI.event.on(btn, "click", function (){ var shot = cam.shot(); // FileAPI.Image instance FileAPI.upload({ url: "...", files: shot }); }); });
  • 81. jQuery.FileAPI • «Одной кнопкой» — выбрать и автоматически загрузить файл • «Ограничения» — минимальный/максимальный размер как файла, так и изображения, по ширине и высоте • «Работа с очередью» — сортировка и фильтрация очереди загрузки файлов • «Изображения» — предпросмотр, поворот и кадрирование • «Интерфейс» — гибкая и прозрачная настройка интерфейса • А также Drag’n’Drop и WebCam

Editor's Notes

  • #2: Добрый день, меня зовут Лебедев Константин, я работаю в компании МэйлРу, проект почта. Сегодня я поделюсь с вами нашим опытом создания open source инструмента для работы с файлами и их загрузки на сервер. По большому счету, это история из серии "а давайте все перепишем на нтмл5" и что из этого получается. Схожие проблемы испытывает любой JS разработчик при внедрении новой технологии, будь это localStorage, или IndexedDB. Везде одни и те же проблемы, это шлейф старых браузеров, разница в имплементации методов, либо их отсутствием или просто баги. Но вернемся к теме доклада. И так, FileAPI.
  • #3: Он позволял выбрать больше одного файла и получить информацию о них, такую как название, тип и размер, а для изображений создавать пред-просмотр.
  • #4: Так же у него есть возможность предварительной обработки изображений: он мог масштабировать и поворачивать. Всё это происходило на клиенте, а результат отправлялось на сервер.
  • #5: Однако у него был ряд недостатков. Это была flash’ка. Вся верстка, графика, бизнес-логика, и даже локализация были зашиты в нем, в результате чего решение было тяжеловесным, а внести правки мог только flash-разработчик.
  • #6: Но это ещё не всё. Так же у нас был второй загрузчик, для тех браузеров, которые не поддерживают flash. Это был обычный js-загрузчик, который позволял выбрать один файл и загрузить его через iframe. Вот и всё что он умел. Как вы понимаете… Вот так, появилась задача переписать наш загрузчик файлов. Тут я бы хотел пояснить, на выходе, мы хотели получить унифицированное API, которое бы работало в независимо от окружения и его можно было использовать не только в рамках проекта “Почта@Mail.ru”, но и где угодно.
  • #7: Для этого API были сформированы следующие требования: Во первых, необходимо сохранить текущую функциональность. Все что умел старый загрузчик, нужно перенести в новый.
  • #8: Во вторых, надо избавиться от недостатков. Бизнес-логика, верстка и тп, не должно содержаться в загрузчике. Только работа с файлами и как можно лучше, все остальное не его задача. В третьих, расширяемость. Если завтра окажется, что нужно получать id3 теги аудио-файла, это не должно стать сюрпризом.
  • #9: Первое, с чего начали, это рассмотрение возможности избавления от flash. Этому есть множество причин, например flash-блокеры или proxy, из-за которых загрузчик становился бесполезным. Так же это ещё одна технология, который имеет свои заморочки и требует поддержки.
  • #10: Первое, с чего начали, это рассмотрение возможности избавления от flash. Этому есть множество причин, например flash-блокеры или proxy, из-за которых загрузчик становился бесполезным. Так же это ещё одна технология, который имеет свои заморочки и требует поддержки.
  • #11: Первое, с чего начали, это рассмотрение возможности избавления от flash. Этому есть множество причин, например flash-блокеры или proxy, из-за которых загрузчик становился бесполезным. Так же это ещё одна технология, который имеет свои заморочки и требует поддержки.
  • #12: Как видите, уже более 87% браузеров поддерживают FileAPI. IE обзаведется поддержкой только с 10 версии. Но 87, это далеко не 95% и даже 90% пользователей, так что от флешь отказываться рано!
  • #13: Таким образом, задача свелась к созданию механизма, который совмещал бы в себе обе технологии и реализованный так, чтобы конечному разработчику было не важно как это происходит. Рассмотрим на конкретных примерах, как проходил процесс разработки.
  • #14: Таким образом, задача свелась к созданию механизма, который совмещал бы в себе обе технологии и реализованный так, чтобы конечному разработчику было не важно как это происходит. Рассмотрим на конкретных примерах, как проходил процесс разработки.
  • #15: На HTML5, это выглядит следующим образом: Получаем ссылку на инпут, далее подписываемся на событие change
  • #16: На HTML5, это выглядит следующим образом: Получаем ссылку на инпут, далее подписываемся на событие change. И когда оно срабатывает, получаем список файлов прямиком из него.
  • #17: Но что делать когда не поддерживается FileAPI, но за то поддерживается Flash? Основным принципом работы с flash, в том, что всё взаимодействие происходит непосредственно через через него, т.е. нельзя просто взять и вызвать диалог выбора файлов. Для этого, необходимо чтобы пользователь кликнул по flash, и только в этот момент открыть диалог, такова политика безопасности, диалог можно открыть только на действие пользователя.
  • #18: Поэтому flash объект размещается над нужным input. Сделано это очень просто, вешается обработчик события mouseover на весь документ и при наведении на input, публикуем flash-объект в его родитель, относительно которого и позиционируем с нужными размерами.
  • #19: При клике по флешке открывается диалог выбора файлов, пользователь там что-то выбирает и кликает «ОК». После чего, данные получает flash, которые всю очередь передает их JS, посредством вызова callback-функции. После чего JS связывает полученные данные с нужным инпутом и эмулирует событие “change”.
  • #20: При клике по флешке открывается диалог выбора файлов, пользователь там что-то выбирает и кликает «ОК». После чего, данные получает flash, которые всю очередь передает их JS, посредством вызова callback-функции. После чего JS связывает полученные данные с нужным инпутом и эмулирует событие “change”.
  • #21: Все дальнейшее взаимодействие между JS и flash, будет осуществляется через единственный доступный метод у flash, первый аргументом передается название команды, вторым объект параметров, с двумя обязательными полями: id-файла и callback. Callback будет вызван из flash, по завершению команды.
  • #22: После совмещения двух способов, получилось вот такое API, оно максимально приближено к нативному js, единственное различие, это способ получения файлов. Теперь мы используем метод API, тк свойство files у инпута есть только в том случае, когда браузер поддерживает HTML5/FileAPI, в случае с Flash список берется из связанных с ним данных.
  • #23: После совмещения двух способов, получилось вот такое API, оно максимально приближено к нативному js, единственное различие, это способ получения файлов. Теперь мы используем метод API, тк свойство files у инпута есть только в том случае, когда браузер поддерживает HTML5/FileAPI, в случае с Flash список берется из связанных с ним данных.
  • #24: Следующим важным шагом, является фильтрация.  Как правило, при загрузке файлов, есть ряд ограничений, таких как размер или тип. Хотя в почте и можно прикреплять любые файлы, для нас было важным получить универсальное решение, так что фильтрация необходима. В чем же её сложность?! Вся соль в том, что изначально, после получения списка файлов, мы имеем только минимальный сведения, такие как название, размер и тип. Но, что делать, когда нужно получить id3 теги или размеры изображения?
  • #25: Все просто, файл нужно прочесть. Для этого в существует FileReader, которые позволяет асинхронно читать файл. И на основе результата чтения, получить нужную информацию, а так как для разных типов файлов, есть свой способ её получения, мы реализовали получение только размеров изображения, как на HTML5, так и через флеш. Но, оставили возможность добавлять свои обработчики, для сбора инфы, в которых разработчик может написать свою реализацию, либо использовать готовое решение.
  • #26: Вот так выглядит метод фильтрации, первым аргументом передаем список файлов, вторым саму функцию фильтрации и третьим callback-который будет вызван по завершению. На выходе получаем два массива, с файлами которые подошли под условия и со всем остальными.
  • #27: Так же существует метод получения информации для конкретного файла.
  • #28: Одной из ключевых особенностей нашего flash-загрузщика, была возможность создавать предросмотр изображений. Проблема была в том, что превью было частью flash, те рисовалась в нем. В API такого быть не должно. Но как передать изображение из Flash в HTML?
  • #29: В этом нам поможет DataURI — это определённая стандартом схема, которая позволяет включать небольшие элементы данных в строку и использовать в качестве ссылки, в данном случае в качестве src изображения.
  • #30: Flash читает файл как Base64, передает результат в JS, который в начало строки добавляет схему, mime-type и метку, что данные в Base64. Теперь мы можем использовать эту строку, как src изображения.
  • #31: Flash читает файл как Base64, передает результат в JS, который в начало строки добавляет схему, mime-type и метку, что данные в Base64. Теперь мы можем использовать эту строку, как src изображения.
  • #32: Flash читает файл как Base64, передает результат в JS, который в начало строки добавляет схему, mime-type и метку, что данные в Base64. Теперь мы можем использовать эту строку, как src изображения.
  • #33: На хтмл5 тоже не без сюрпризов,  есть два способа получить изображение. Первый способ, это прочесть файл как DataURL, при помощи FileReader. Второй, createObjectURL — создает ссылку на файл, связанную с текущим табом. Конечно для создания пердпросмотра достаточно второго способа, но не все бразуеры его поддерживают.
  • #34: Так же у Оперы нет метода revokeObjectURL, который сообщает браузеру, что больше не нужно держать ссылку на файл.
  • #35: Класс, обладает следующими методами: кадрирование, масштабирование, поворот и preview, который автоматически кадрирует и масштабирует. Все эти методы заполняют матрицу трансформации и только при вызове метода get, она будет применена. Трансформация происходит через canvas или внутри flash, когда работает через него.
  • #36: Трансформация происходит через canvas или внутри flash, когда работает через него. Вот так выглядит та самая матрица,
  • #37: Вот так выглядит создание пердпросмотра 120х120 пикселей, вызываем метод preview, передаем требуемые размеры и вызываем метод get, на вход которого передаем callback, который будет вызван по завершению.
  • #38: Отдельно отмечу процесс ресайза. Если нужно получить превью 100х100, а оригинал примерно 10 МПх, а это вполне реальная ситуация, сейчас каждая мыльница кичится количеством мега-пикселей, то вы получите изображение, примерно такого качества.
  • #39: Как видите, ничего хорошего, сплошные искажения.
  • #40: Но, если сжимать в два раза, потом ещё и ещё, пока не получим требуем размер, то результат заметно лучше.
  • #41: Но, если сжимать в два раза, потом ещё и ещё, пока не получим требуем размер, то результат заметно лучше. Вот, сравните, разница на лицо.
  • #42: Но, если сжимать в два раза, потом ещё и ещё, пока не получим требуем размер, то результат заметно лучше. Вот, сравните, разница на лицо.
  • #43: Еще мы пробовали другие способы, такие как бикубическая интерполяция и алгоритм Ланцоша. Они конечно дают лучший результат, но очень медленные, 1.5сек против 200-300милисекунд. Также данный метод дает одинаковый результат в cavnas и flash.
  • #44: Последняя и основная возможность нашего API, это конечно же загрузка файлов на сервер.
  • #45: Вот так выглядит самый простой способ загрузки файлов.
  • #46: Для улучшение ситуация, добавляем в форму iframe с уникальным именем, а у формы такой же target. Теперь при сабмите, форма будет отправлена в этот ифрейм. Этот способ позволяет избавиться избавиться от обновления страницы, также работает во всех браузерах, есть возможность получить ответ от сервера, но не дает сведений о ходе загрузки.
  • #47: Последний способ, отправить файл на сервер, не считая flash, это XmlHttpRequest2 и FormData. Он позволяет отправить не просто тестовые данные, но и бинарные.
  • #48: FormData — позволяет создать набор ключ-значение. У неё есть единственный метод append, первым аргументом задаётся название POST-параметра, вторым строка или файл, также есть третий аргумент, название файла, но его пока не все поддерживают.
  • #49: Далее при помощи XmlHttpRequest отправляет собранные данные на сервер. Но ничего не бывает так просто. Как я уже говорил ранее, API должно уметь отправлять модифицированное изображение.
  • #50: Т.к. все трансформации идут через canvas, то именно его и нужно отправить на сервер и это можно сделать, при помощи трансформации canvas в blob, её можно реализовать практически во всех браузерах. Но там, где это невозможно, а это например Opera, мы загружаем cavnas ручного составления мултипарт запроса. И в этом случае, мы делаем следующее:
  • #51: Сначала конвертируем canvas в DataURL, а DataURLв Base64, после чего получаем BinaryString. А дальше самое интересное, руками составляем мультипарт запрос и отправляем его на сервер.
  • #52: Сначала конвертируем canvas в DataURL, а DataURLв Base64, после чего получаем BinaryString. А дальше самое интересное, руками составляем мультипарт запрос и отправляем его на сервер.
  • #53: Сначала конвертируем canvas в DataURL, а DataURLв Base64, после чего получаем BinaryString. А дальше самое интересное, руками составляем мультипарт запрос и отправляем его на сервер.
  • #54: Справедливости ради замечу, что данный способ используется и для тех браузеров, которые не поддерживают FormData. Т.е. При помощи FileReader читаем файл как DataURL и по такой же схеме отправляем их на сервер.
  • #55: На вход ему передается объект параметров: «куда заливать», «дополнительная POST-дата» и «заголовки запроса», также обработчики событий, как для слежения за ходом всей загрузки, так и отдельным файлом. Хотя HTML5 позволяет грузить все файлы сразу, стандартный механизм Flash’а нет, плюс это не очень удачная идея, пользователь может и передумать.
  • #56: На вход ему передается объект параметров: «куда заливать», «дополнительная POST-дата» и «заголовки запроса», также обработчики событий, как для слежения за ходом всей загрузки, так и отдельным файлом. Хотя HTML5 позволяет грузить все файлы сразу, стандартный механизм Flash’а нет, плюс это не очень удачная идея, пользователь может и передумать.
  • #57: Это позволяет перенести часть нагрузки по обработке изображений с сервера, на клиент.
  • #58: Так же функции upload возвращает xhr-образный объект, т.е. он реализует некоторые свойства и методы XmlHttpRequest, такие как
  • #59: Но это еще не все, на самом деле, это прокси объект. Его методы и свойства отражают состояния именно для того файла, который грузится в данный момент.
  • #67: 100 задач, это примерно 2 задачи в неделю. Будьте готовы отвечать на самые разнообразные вопросы, некоторые будут глупыми, другие полезными. Людей много, их задачи очень разные. Но всё это только улучшает как код библиотеки, так и её возможности.