— Pixels Commander

[ In English, На русском ]

Лучший способ загрузки файлов в JavaScript

Скачивание файла — что может быть проще? Однако как и везде — в этом вопросе есть подводные камни и нет предела совершенству. В статье описывается лучший способ скачать файл в JavaScript.

Если вам не нужна теория о загрузке файлов в JavaScript и вы просто хотите подключить функцию downloadFile к проекту — вам на GitHub.

Как правило в HTML среде используются два пути вызова загрузки: window.open и клик по ссылке на файл. Каждый из способов по своему ущербный. В поиске универсальной техники было найдено несколько интересных решений, все вместе они составляют лучший метод для скачивания файлов из JS.

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

window.downloadFile = function(sUrl) {
    window.open(sUrl);
}

Эта функция будет работать везде, но с рядом недостатков:

  • В Chrome и Safari после срабатывания останется пустое окно;
  • Если content-type файла предполагает открытие то файл не скачается, а отобразится в открывшемся окне.

Что же мы можем предпринять?

Аргумент «_self» для window.open

Убрать назойливое окно в Chrome и Safari можно добавив аргумент «_self» к window.open:

window.downloadFile = function(sUrl) {
    window.open(sUrl, '_self');
}

 

Виртуальный клик

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

Добавим код виртуального щелчка для хрома и сафари, оставляя за кадром банальное определение браузера:

if (window.downloadFile.isChrome || window.downloadFile.isSafari) {
    //Creating new link node.
    var link = document.createElement('a');
    link.href = sUrl;

    //Dispatching click event.
    if (document.createEvent) {
        var e = document.createEvent('MouseEvents');
        e.initEvent('click' ,true ,true);
        link.dispatchEvent(e);
        return true;
    }
}

HTML5 аттрибут download и игнорирование content-type Что еще может вызвать раздражение пользователя? Например ситуация, когда при загрузке HTML или PNG файла он открывается в браузере. Этого можно избежать добавивши к нашему виртуальному линку аттрибут download, который появился в HTML5 и служит для указания браузеру на «скачивающий» характер ссылки. Значение аттрибута — это итоговое имя скачиваемого файла, а href — исходный URL.   Итоговый код модуля download.js:

window.downloadFile = function(sUrl) {

    //If in Chrome or Safari - download via virtual link click
    if (window.downloadFile.isChrome || window.downloadFile.isSafari) {
        //Creating new link node.
        var link = document.createElement('a');
        link.href = sUrl;

        if (link.download !== undefined){
            //Set HTML5 download attribute. This will prevent file from opening if supported.
            var fileName = sUrl.substring(sUrl.lastIndexOf('/') + 1, sUrl.length);
            link.download = fileName;
        }

        //Dispatching click event.
        if (document.createEvent) {
            var e = document.createEvent('MouseEvents');
            e.initEvent('click' ,true ,true);
            link.dispatchEvent(e);
            return true;
        }
    }

    // Force file download (whether supported by server).
    var query = '?download';

    window.open(sUrl + query, '_self');
}

window.downloadFile.isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
window.downloadFile.isSafari = navigator.userAgent.toLowerCase().indexOf('safari') > -1;

 

Пример использования

Проект на GitHub

  • Maxim Kachurovskiy

    The canonical way of serving file as an attachment is «Content-Disposition: attachment» header — http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1 though it’s not a solution when you don’t control the server.

    Header also has one more advantage — it prevents some file types e.g. SWF from being launched/embedded which is helpful preventing a bunch of attacks.

  • Аноним

    Yes, definitely, Content-Disposition is right way to go if you control server side, however it`s always good to have control from JavaScript land. Thanks for remark, I`ll edit article a bit.

  • Александр Иванов

    Для того чтобы избежать открытия нового окна можно добавить вторым параметром ‘_self’ — window.open(sUrl, ‘_self’);

  • Аноним

    Спасибо, добавил этот пункт в статью.

  • Rick

    Thank you for this code. It works great for Chrome in Windows but with Chrome on iPad the file opens in the browser instead of downloading. Any ideas on how to fix this?

  • Аноним

    As I know there is no ways to download file from iPad browser. Newest snippet version have alert message in case of such attempts.

    Also seems that you need to modify headers on server side.

    Easiest way to configure headers via Apache is to set Header set Content-Disposition «attachment» for files you want to be downloaded.

    So .htaccess can look like:

    Header set Content-Disposition attachment

  • Rick

    Thanks for the update which informs the iPad user about downloads, it’s very helpful.

  • anil vemula

    Nuvvu devudu samee…..

  • Аноним

    Just opens another tab in latest Chrome…

  • Аноним

    Just tried to run example from repository. It works as expected. Lets determine you issue, drop me a message in skype lp_funky.

  • Аноним

    It works now. Used error on my part. Nice code.

  • http://www.gregory.goltsov.info/ Gregory Goltsov

    Thanks for a great article! However your syntax highlighter seems to be slightly broken: in the last code snipped, the «>» sign is mangled, i.e.:


    .indexOf('chrome') & gt; -1;

  • Аноним

    Thanks, fixed

  • Adam Simms

    Thank you so much for this!

  • Maxim

    Another way is to create hidden form with action = «url-to-file» and then submit it.

  • http://www.jasaaplikasiandroid.com/ Hedi Herdiana

    Hi, it’s not working in webview cordova of android app.

  • Andrei

    А возможно задать директорию для скачивающегося файла?

  • Аноним

    Нет, к сожалению в браузерах такая возможность не предусмотрена.

  • sriram s

    Downloading file is working fine.i need to download the file with its folder-javascript

  • G

    Same is not working as chrome extension. I added manifest.json. It opens the html page. Does not display
    label.innerHTML = ‘Download will start in ‘ + secondsBeforeDownloading + ‘ seconds…’;