Особенности загрузки файлов на HTML5

в 19:32, , рубрики: file reader, html, html5 file api, javascript, метки: , ,

После некоторого, опыта решил написать небольшую статью-шпаргалку о загрузке файлов с использованием возможностей HTML5, а именно File API.

Читайте далее:

  1. Поддержка браузерами.
  2. Загрузка через Form Data.
  3. Загрузка через File Reader.

1. Поддержка браузерами

Конечно же не все браузеры на данный момент поддерживают в полной мере эту возможность. Про IE благополучно забываем, так как он будет поддерживать File API только в 10 версии. С остальными, более прогрессивными браузерами, а это всего лишь Chrome, Firefox, Opera и Safari, тоже не все так гладко. Они конечно стараются быть «вперде», но пока не у всех получается.

В HTML 5 существует 2 способа считать и асинхронно отправить файл на сервер: через объекты File Reader и Form Data. В принципе, именно для загрузки файлов разницы нет, каким способом отправлять файлы на сервер. Если говорить совсем точно, то за отправку файлов несет ответственность объект XMLHttpRequest, а File Reader и Form Data всего лишь позволяют считать файл и «скормить» его XHR. Эти объекты предназначены для разных задач. File Reader предназначен для работы с файлами на стороне браузера. То есть еще до загрузки файла на сервер можно узнать его параметры, такие как вес, тип, дата создания и т. п. Картинки, например, можно сразу показывать пользователю, не загружая их на сервер. Form Data предназначен для создания форм и управления данными форм до загрузки на сервер. А как мы знаем, в формах и присутствует input type=«file», через который до этого момента мы и отправляли файлы. Таким образом можно создать форму, прикрепить к ней файл и через XHR отправить ее на сервер.

От лирики к делу. Рассмотрим оба примера загрузки. Но прежде небольшая заметка о том, что неплохо было бы определять возможности браузера по загрузке через File API. Для этого достаточно проверить наличие объектов File Reader или Form Data. Но! Safari до сих пор не сделал поддержку File Reader и работать с этим объектом у вас не получится. Яблофаги негодуют, а мы любим всех пользователей одинаково. Поэтому на данный момент самой правильной проверкой будет считаться наличие Form Data:

if (window.FormData === undefined) {
    // Сообщаем отсталым браузерам, что они отсталые :-) Либо делаем альтернативную загрузку.
}

2. Загрузка через Form Data

Сначала описываем все события: перетаскивание файлов, «бросание» их в блок для загрузки и т. п.

$("#drop-block").bind( // #drop-block блок куда мы будем перетаскивать наши файлы
    'dragenter',
    function(e) {
        // Действия при входе курсора с файлами  в блок.
    }) .bind(
    'dragover',
    function(e) {
        // Действия при перемещении курсора с файлами над блоком.
    }).bind(
    'dragleave',
    function(e) {
        // Действия при выходе курсора с файлами за пределы блока.
    }).bind(
    'drop',
    function(e) { // Действия при «вбросе» файлов в блок.
        if (e.originalEvent.dataTransfer.files.length) {
            
            // Отменяем реакцию браузера по-умолчанию на перетаскивание файлов.
            e.preventDefault();
            e.stopPropagation();

            // e.originalEvent.dataTransfer.files — массив файлов переданных в браузер.
            // e.originalEvent.dataTransfer.files[i].size — размер отдельного файла в байтах.
            // e.originalEvent.dataTransfer.files[i].name — имя отдельного файла.
            // Что какбэ намекает :-)
                    
            upload(e.originalEvent.dataTransfer.files); // Функция загрузки файлов.
        }
    });

Ну и закодим непосредственно чтение и загрузку файлов:

function upload(files) {

    // Сначала мы отправим пустой запрос на сервер. 
    // Это связано с тем, что иногда Safari некорректно обрабатывает
    // первый файл.

    $.get('/blank.html');

    var http = new XMLHttpRequest(); // Создаем объект XHR, через который далее скинем файлы на сервер.

    // Процесс загрузки
    if (http.upload && http.upload.addEventListener) {

        http.upload.addEventListener( // Создаем обработчик события в процессе загрузки.
        'progress',
        function(e) {
            if (e.lengthComputable) {
                // e.loaded — сколько байтов загружено.
                // e.total — общее количество байтов загружаемых файлов.
                // Кто не понял — можно сделать прогресс-бар :-)
            }
         },
        false
        );

        http.onreadystatechange = function () {
            // Действия после загрузки файлов
            if (this.readyState == 4) { // Считываем только 4 результат, так как их 4 штуки и полная инфа о загрузке находится
                if(this.status == 200) { // Если все прошло гладко

                    // Действия после успешной загрузки.
                    // Например, так
                    // var result = $.parseJSON(this.response);
                    // можно получить ответ с сервера после загрузки.

                } else {
                    // Сообщаем об ошибке загрузки либо предпринимаем меры.
                }
            }
        };

        http.upload.addEventListener(
        'load',
        function(e) {
            // Событие после которого также можно сообщить о загрузке файлов.
            // Но ответа с сервера уже не будет.
            // Можно удалить.
        });

        http.upload.addEventListener(
        'error',
        function(e) {
            // Паникуем, если возникла ошибка!
        });
    }

    var form = new FormData(); // Создаем объект формы.
    form.append('path', '/'); // Определяем корневой путь.
    for (var i = 0; i < files.length; i++) {
        form.append('file[]', files[i]); // Прикрепляем к форме все загружаемые файлы.
    }
    http.open('POST', '/upload.php'); // Открываем коннект до сервера.
    http.send(form); // И отправляем форму, в которой наши файлы. Через XHR.
}

3. Загрузка через File Reader

File Reader собственно и предназначен для работы с файлами на стороне браузера. Попробуем загрузить файлы при помощи этого объекта. Напоминаем, что в Safari нет объекта File Reader!

if (window.FileReader === undefined) { // Проверяем наличие объекта в браузере
    // Если нет — негодуем!
}

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

var dropbox = $('#file-drag');

dropbox[0].ondragover = function() {

    return false;
};

dropbox[0].ondragleave = function() {

    return false;
};

dropbox[0].ondrop = function(e) {
    var files = e.dataTransfer.files;
    uploadFile(files[i]);
    e.preventDefault();
    e.stopPropagation();
    return false;
};

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

// Функция чтения и загрузки

function uploadFile(file) {
    var reader = new FileReader();
    reader.onload = function() {
        var xhr = new XMLHttpRequest();
        xhr.upload.addEventListener("progress-bar", function(e) {
            if (e.lengthComputable) {
                // Прогресс-бра, ага ;-)
            }
        }, false);

        // Помним про события load и error

        xhr.onreadystatechange = function () {
            if (this.readyState == 4) {
                if(this.status == 200) {

                } else {
                    // Пичалька :-(
                }
            }
        };

        xhr.open("POST", "upload.php");

        // Составляем заголовки и тело запроса к серверу,  в котором и отправим файл.

        var boundary = "xxxxxxxxx";
        // Устанавливаем заголовки.
        xhr.setRequestHeader('Content-type', 'multipart/form-data; boundary="' + boundary + '"');
        xhr.setRequestHeader('Cache-Control', 'no-cache');

        // Формируем тело запроса.
        var body = "--" + boundary + "rn";
        body += "Content-Disposition: form-data; name='superfile'; filename='" + unescape( encodeURIComponent(file.name)) + "'rn"; // unescape позволит отправлять файлы с русскоязычными именами без проблем.
        body += "Content-Type: application/octet-streamrnrn";
        body += reader.result + "rn";
        body += "--" + boundary + "--";

        // Пилюля от слабоумия для Chrome, который гад портит файлы в процессе загрузки.		
        if (!XMLHttpRequest.prototype.sendAsBinary) {
            XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
                function byteValue(x) {
                    return x.charCodeAt(0) & 0xff;
                }
                var ords = Array.prototype.map.call(datastr, byteValue);
                var ui8a = new Uint8Array(ords);
                this.send(ui8a.buffer);
            }
        }

        // Отправляем файлы.
        if(xhr.sendAsBinary) {
            // Только для Firefox
            xhr.sendAsBinary(body);
        } else {
            // Для остальных (как нужно по спецификации W3C)
            xhr.send(body);
        }
    };
                
    // Читаем файл
    reader.readAsBinaryString(file);
};

Данная статья является не инструкцией к применению, а всего лишь пособием для изучения возможностей HTML 5. Код не претендует на идеальность. Буду рад, если кто-то дополнит или поправит.

Автор: dijay

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js