константин лебедев
FileAPI: Загрузка и обработка файлов
Что было




           3
Что было




           4
Что было




           Flash
                   5
Что было




           HTML/JS
                     6
Требования

  •   Множественный выбор файлов
  •   Получение информации (название, размер, тип)
  •   Создание пред-просмотра на клиенте
  •   Масштабирование, кадрирование и поворот
  •   Загрузка на сервер + CORS


                                                     7
Требования

  •   Не зависеть от вѐрстки
  •   Никакой бизнес-логики
  •   Расширяемость
  •   Самодостаточность




                               8
константин лебедев
Error #2038

10%
Error #2038

  5%
Поддержка

            •   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.
 }])                                                                   20
Взаимодействие




    flash.cmd("imageTransform", {
            id:       "346515436346", // идентификатор файла
            matrix: { },                  // "матрица" трансформации
            callback: "__UNIQ_NAME__" // размер
    });
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);
         }, false);
 </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);
         }, false);
 </script>
                                                                23
ФИЛЬТРАЦИЯ


             24
FileReader

  • readAsDataURL(file)
  • readAsBinaryString(file)
  • readAsArrayBuffer(file)
  • readAsText(file[, encoding])
Фильтрация
     FileAPI.filterFiles(files, function (file, info){

              return file.size < 10 * FileAPI.MB;

     }, function (files, ignore){
              if( files.length > 0 ){
                       // ...
              }
     });
                                                         26
Информация о файле

 FileAPI.addInfoReader(/^audio/, function (file, callback){
         // собираем нужную информацию
         // и возвращаем еѐ
         callback(
                 false,    // или текст ошибки
                 { artist: "...", album: "...", title: "...", ... }
         );
 });
Информация о файле

  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

                                       <img/>
         Base64

                  ―data:image/png;base64,‖ + Base64
Предпросмотр

           DataURI

                                       <img/>
         Base64

                  ―data:image/png;base64,‖ + Base64
Предпросмотр

 HTML5
  • FileReader.readAsDataURL(file) — позволяет
    прочесть содержимое файла как DataURL
  • URL.createObjectURL(file) — создает
    ссылку, указывающую на файл
Предпросмотр

 HTML5
  • FileReader.readAsDataURL(file) — позволяет
    прочесть содержимое файла как DataURL
  • URL.createObjectURL(file) — создает
    ссылку, указывающую на файл
  • URL.revokeObjectURL(file) — убрать ссылку
FileAPI.Image

  • crop(x, y, width, height) — кадрирование
  • resize(width[, height]) — масштабирование
  • rotate(deg) — поворот
  • preview(width, height) — кадрирует и масштабирует
  • get(callback) — получить итоговое изображение


                                                        36
Matrix
  {      // параметры фрагмента оригинала
         sx: Number,
         sy: Number,
         sw: Number,
         sh: Number,

         // требуемые размеры
         dw: Number,
         dh: Number,
         deg: Number
  }
                                            37
FileAPI.Image
   FileAPI.Image(imageFle)
           .crop(300, 300)
           .resize(100, 100)
           .get(function (err, img){
                   if( !err ){
                              images.appendChild(img);
                   }
           })
   ;

                                                         38
Сжатие




         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
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'));
                                                                                  56
Загрузка
 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){}
 });​
                                                          57
Загрузка
 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){}
 });​
                                                          58
Загрузка
 imageTransform:
    {
        huge:    { maxWidth: 800, maxHeight: 600, rotate: 90 },
        medium: { width: 320, height: 240, preview: true },
        small:   { width: 100, height: 120, preview: true }
    }
XHR
var xhr = FileAPI.upload({ … });




                                   60
XHR
var xhr = FileAPI.upload({ … });

 • status — HTTP status code
 • statusText — HTTP status text
 • responseText — ответ сервера
 • getResponseHeader(name) — получить заголовок ответа сервера
 • getAllResponseHeaders() — получить все заголовки
 • abort() — отменить загрузку


                                                            61
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 {                                     4
           el.classList.remove("dropzone_hover");
       }
   }, function (files){
         uploadFiles(files);
   });
 </script>
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");
                                                    4
       } else {
           el.classList.remove("dropzone_hover");
       }
   }, function (files){
         uploadFiles(files);
   });
 </script>
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");
                                                    4
       } else {
           el.classList.remove("dropzone_hover");
       }
   }, function (files){
         uploadFiles(files);
   });
 </script>
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");
                                                    4
       } else {
           el.classList.remove("dropzone_hover");
       }
   }, function (files){
         uploadFiles(files);
   });
 </script>
Drag’n’Drop

  function uploadFiles(dropFiles){
    FileAPI.upload({
        url: "/upload",
        files: { attaches: dropFiles },
        complete: function (err, xhr){
             if( !err ){                  4
                  // файлы загружены
             }
        }
    });
  }
https://github.com/mailru/FileAPI
      Константин Лебедев
     JavaScript архитектор
     k.lebedev@corp.mail.ru

More Related Content

PDF
Школа-студия разработки для iOS. Лекция 4. Работа с данными
PDF
PPT
Drupal 7 and History.js
PDF
Укрощение XML
PPT
Drupal 7 и history.js или как ajax инфицировать сайт
PPT
Web весна 2012 лекция 4
PPT
Эффективное программирование на NodeJS
Школа-студия разработки для iOS. Лекция 4. Работа с данными
Drupal 7 and History.js
Укрощение XML
Drupal 7 и history.js или как ajax инфицировать сайт
Web весна 2012 лекция 4
Эффективное программирование на NodeJS

What's hot (19)

PPTX
MongoDB - About Performance Optimization, Ivan Griga - Smart Gamma
PPT
Web осень 2012 лекция 4
PDF
C# Desktop. Занятие 11.
PPT
JavaScript-библиотека
PDF
Next Gen Applications
PDF
Хранение, обработка и отдача статики с использованием \Zend\File. Опыт социал...
PDF
Примеры решения типичных задач за рамками ядра Yii2
PPT
CodeFest 2013. Никонов Г. — Как мы разрабатываем приложения для Windows Phone...
PPT
LDAP in infrastructure (RootConf 2009)
PDF
JS утиліти WordPress на практиці
PDF
Магия метаклассов
PDF
basis.js - почему я не бросил разрабатывать свой фреймворк
PDF
YiiConf: Миграции и инсталляции
PDF
Продвинутое использование ActiveRecord в Yii2
PDF
Встраивание языка в строковой интерполятор
PDF
10 - Web-технологии. MVC фреймворки (продолжение)
PDF
Python dict: прошлое, настоящее, будущее
PDF
диплом
PDF
хранение данных
MongoDB - About Performance Optimization, Ivan Griga - Smart Gamma
Web осень 2012 лекция 4
C# Desktop. Занятие 11.
JavaScript-библиотека
Next Gen Applications
Хранение, обработка и отдача статики с использованием \Zend\File. Опыт социал...
Примеры решения типичных задач за рамками ядра Yii2
CodeFest 2013. Никонов Г. — Как мы разрабатываем приложения для Windows Phone...
LDAP in infrastructure (RootConf 2009)
JS утиліти WordPress на практиці
Магия метаклассов
basis.js - почему я не бросил разрабатывать свой фреймворк
YiiConf: Миграции и инсталляции
Продвинутое использование ActiveRecord в Yii2
Встраивание языка в строковой интерполятор
10 - Web-технологии. MVC фреймворки (продолжение)
Python dict: прошлое, настоящее, будущее
диплом
хранение данных
Ad

Similar to константин лебедев (10)

PDF
FrontTalks: Константин Лебедев (Mail.ru), File API: обработка файлов на клиен...
PDF
Марина Степанова "Как мы заставили API Яндекс.Карт работать быстрее"
PDF
Алексей Захаров "Архитектура Яндекс.Фоток"
PPTX
О безопасном использовании PHP wrappers
PDF
Михаил Давыдов — Транспорт, Ajax
PDF
Promise me an Image... Антон Корзунов, Яндекс, MoscowJs 33
PDF
Mihail davidov js-ajax
PPT
Js Http Request дмитрий котеров
ODP
Yandex Lego олег оболенский
PPTX
Взломать сайт на ASP.NET
FrontTalks: Константин Лебедев (Mail.ru), File API: обработка файлов на клиен...
Марина Степанова "Как мы заставили API Яндекс.Карт работать быстрее"
Алексей Захаров "Архитектура Яндекс.Фоток"
О безопасном использовании PHP wrappers
Михаил Давыдов — Транспорт, Ajax
Promise me an Image... Антон Корзунов, Яндекс, MoscowJs 33
Mihail davidov js-ajax
Js Http Request дмитрий котеров
Yandex Lego олег оболенский
Взломать сайт на ASP.NET
Ad

More from kuchinskaya (20)

PDF
Kharkov
PDF
Balashov
PDF
Zamyakin
PDF
Panfilov
PDF
Platov
PDF
Rabovoluk
PDF
Smirnov dependency-injection-techforum(1)
PDF
Smirnov reverse-engineering-techforum
PDF
Zacepin
PDF
Zagursky
PDF
Haritonov
PDF
Chudov
PDF
Bubnov
PDF
A.pleshkov
PDF
Zenovich
PDF
Romanenko
PDF
Perepelitsa
PDF
Osipov
PDF
Kubasov
PDF
Kalugin balashov
Kharkov
Balashov
Zamyakin
Panfilov
Platov
Rabovoluk
Smirnov dependency-injection-techforum(1)
Smirnov reverse-engineering-techforum
Zacepin
Zagursky
Haritonov
Chudov
Bubnov
A.pleshkov
Zenovich
Romanenko
Perepelitsa
Osipov
Kubasov
Kalugin balashov

константин лебедев

  • 2. FileAPI: Загрузка и обработка файлов
  • 5. Что было Flash 5
  • 6. Что было HTML/JS 6
  • 7. Требования • Множественный выбор файлов • Получение информации (название, размер, тип) • Создание пред-просмотра на клиенте • Масштабирование, кадрирование и поворот • Загрузка на сервер + CORS 7
  • 8. Требования • Не зависеть от вѐрстки • Никакой бизнес-логики • Расширяемость • Самодостаточность 8
  • 12. Поддержка • Chrome 10+ • FireFox 3.6+ • Opera 11.1+ • Safari 5.4+ 12
  • 13. 13
  • 15. HTML5 <input id="file" type="file" multiple /> <script> var input = document.getByElementId("file"); input.addEventListener("change", function (){ var files = input.files; }, false); </script> 15
  • 16. HTML5 <input id="file" type="file" multiple /> <script> var input = document.getByElementId("file"); input.addEventListener("change", function (){ var files = input.files; }, false); </script> 16
  • 17. HTML5 <input id="file" type="file" multiple /> <script> var input = document.getByElementId("file"); input.addEventListener("change", function (){ var files = input.files; }, false); </script> 17
  • 18. Flash FLASH 18
  • 19. Flash FLASH 19
  • 20. Flash FLASH Flash --> jsFunc([{ id: "346515436346", // уникальный идентификатор name: "hello-world.png", // название файла type: "image/png", // mime-type size: 43325 // рамер }, { // etc. }]) 20
  • 21. Взаимодействие flash.cmd("imageTransform", { id: "346515436346", // идентификатор файла matrix: { }, // "матрица" трансформации callback: "__UNIQ_NAME__" // размер });
  • 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 (){ var files = FileAPI.getFiles(input); }, false); </script> 22
  • 23. 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); }, false); </script> 23
  • 25. FileReader • readAsDataURL(file) • readAsBinaryString(file) • readAsArrayBuffer(file) • readAsText(file[, encoding])
  • 26. Фильтрация FileAPI.filterFiles(files, function (file, info){ return file.size < 10 * FileAPI.MB; }, function (files, ignore){ if( files.length > 0 ){ // ... } }); 26
  • 27. Информация о файле FileAPI.addInfoReader(/^audio/, function (file, callback){ // собираем нужную информацию // и возвращаем еѐ callback( false, // или текст ошибки { artist: "...", album: "...", title: "...", ... } ); });
  • 28. Информация о файле FileAPI.getInfo(audioFile, function (err, tags){ if( !err ){ var li = document.createElement("li"); li.innerHTML = tags.artist +" – "+ tags.title; ul.appendChild(li); } });
  • 31. Предпросмотр DataURI Base64
  • 32. Предпросмотр DataURI <img/> Base64 ―data:image/png;base64,‖ + Base64
  • 33. Предпросмотр DataURI <img/> Base64 ―data:image/png;base64,‖ + Base64
  • 34. Предпросмотр HTML5 • FileReader.readAsDataURL(file) — позволяет прочесть содержимое файла как DataURL • URL.createObjectURL(file) — создает ссылку, указывающую на файл
  • 35. Предпросмотр HTML5 • FileReader.readAsDataURL(file) — позволяет прочесть содержимое файла как DataURL • URL.createObjectURL(file) — создает ссылку, указывающую на файл • URL.revokeObjectURL(file) — убрать ссылку
  • 36. FileAPI.Image • crop(x, y, width, height) — кадрирование • resize(width[, height]) — масштабирование • rotate(deg) — поворот • preview(width, height) — кадрирует и масштабирует • get(callback) — получить итоговое изображение 36
  • 37. Matrix { // параметры фрагмента оригинала sx: Number, sy: Number, sw: Number, sh: Number, // требуемые размеры dw: Number, dh: Number, deg: Number } 37
  • 38. FileAPI.Image FileAPI.Image(imageFle) .crop(300, 300) .resize(100, 100) .get(function (err, img){ if( !err ){ images.appendChild(img); } }) ; 38
  • 39. Сжатие 5197х4987
  • 41. Сжатие 5197х4987 2598х2493
  • 42. Сжатие x 2 5197х4987 2598х2493 1299х1246
  • 43. Сжатие x 5 5197х4987 2598х2493 1299х1246 100х100
  • 44. Сжатие Серия
  • 46. Требования • Без обновления страницы • Процесс загрузки • Получить ответ от сервера
  • 47. Загрузка <form action="/upload" method="post" enctype="multipart/form-data"> <input name="files" type="file" /> <input name="foo" value="bar" type="hidden" /> </form>
  • 48. Загрузка <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>
  • 49. Загрузка XMLHttpRequest level 2 FormData
  • 50. Загрузка // собираем данные для отправки var form = new FormData form.append("foo", "bar"); form.append("attach", file); // отправояем на сервер var xhr = new XMLHttpRequest; xhr.open("POST", "/upload", true); xhr.send(form)
  • 51. Загрузка // собираем данные для отправки var form = new FormData form.append("foo", "bar"); form.append("attach", file); // отправляем на сервер var xhr = new XMLHttpRequest; xhr.open("POST", "/upload", true); xhr.send(form)
  • 52. Загрузка 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) });
  • 54. Загрузка dataURL = canvas.toDataURL(―image/png‖); base64 = dataURL.replace(/^data:[^,]+,/, ―‖); <cavnas/> DataURL Base64
  • 55. Загрузка dataURL = canvas.toDataURL(―image/png‖); base64 = dataURL.replace(/^data:[^,]+,/, ―‖); binaryString = window.atob(base64); <cavnas/> DataURL Base64 BinaryString
  • 56. 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')); 56
  • 57. Загрузка 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){} });​ 57
  • 58. Загрузка 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){} });​ 58
  • 59. Загрузка imageTransform: { huge: { maxWidth: 800, maxHeight: 600, rotate: 90 }, medium: { width: 320, height: 240, preview: true }, small: { width: 100, height: 120, preview: true } }
  • 60. XHR var xhr = FileAPI.upload({ … }); 60
  • 61. XHR var xhr = FileAPI.upload({ … }); • status — HTTP status code • statusText — HTTP status text • responseText — ответ сервера • getResponseHeader(name) — получить заголовок ответа сервера • getAllResponseHeaders() — получить все заголовки • abort() — отменить загрузку 61
  • 62. Drag’n’Drop <div class="dropzone"></div> Перетащите файлы сюда
  • 63. Drag’n’Drop <div class="dropzone dropzone_hover"></div> 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 { 4 el.classList.remove("dropzone_hover"); } }, function (files){ uploadFiles(files); }); </script>
  • 65. 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"); 4 } else { el.classList.remove("dropzone_hover"); } }, function (files){ uploadFiles(files); }); </script>
  • 66. 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"); 4 } else { el.classList.remove("dropzone_hover"); } }, function (files){ uploadFiles(files); }); </script>
  • 67. 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"); 4 } else { el.classList.remove("dropzone_hover"); } }, function (files){ uploadFiles(files); }); </script>
  • 68. Drag’n’Drop function uploadFiles(dropFiles){ FileAPI.upload({ url: "/upload", files: { attaches: dropFiles }, complete: function (err, xhr){ if( !err ){ 4 // файлы загружены } } }); }
  • 69. https://github.com/mailru/FileAPI Константин Лебедев JavaScript архитектор k.lebedev@corp.mail.ru

Editor's Notes

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