предисловие
Скачивание файлов на фронтенд-сайте крайне распространенное требование.Решения разные давно были.Почему вы до сих пор пишете такую старую статью?Просто я недавно привожу нового человека.Когда мы придем к типичная проблема "не могу скачать txt, png и другие файлы", я дам ему краткое описание нескольких способов загрузки. Поделитесь кстати, может кому очень нужно.
форма отправки формы
Это традиционный метод, который часто использовался в прошлом, ведь в ту эпоху было не так много новых функций, которые были бы просты в использовании.
Причина тоже очень проста, добавить кнопку загрузкиclick
событие, динамически генерировать форму при нажатии и использовать функцию отправки формы для загрузки файла (фактически отправка формы - это отправка запроса)
Давайте посмотрим, как сгенерировать форму и какая форма генерируется:
/**
* 下载文件
* @param {String} path - 请求的地址
* @param {String} fileName - 文件名
*/
function downloadFile (downloadUrl, fileName) {
// 创建表单
const formObj = document.createElement('form');
formObj.action = downloadUrl;
formObj.method = 'get';
formObj.style.display = 'none';
// 创建input,主要是起传参作用
const formItem = document.createElement('input');
formItem.value = fileName; // 传参的值
formItem.name = 'fileName'; // 传参的字段名
// 插入到网页中
formObj.appendChild(formItem);
document.body.appendChild(formObj);
formObj.submit(); // 发送请求
document.body.removeChild(formObj); // 发送完清除掉
}
преимущество
- Традиционный метод, хорошая совместимость, отсутствие проблем с ограничением длины URL
недостаток
- Невозможно узнать ход загрузки
- Невозможно напрямую загрузить типы файлов, которые браузеры могут просматривать напрямую (например, txt/png и т. д.)
открыть или location.href
На самом деле самый простой и прямой способ — следоватьa
Отметьте доступ к ссылкам для скачивания, например
window.open('downloadFile.zip');
location.href = 'downloadFile.zip';
Конечно, адрес также может быть адресом интерфейса API, а не просто адресом ссылки.
преимущество
- Просто и удобно
недостаток
- Будет проблема с ограничением длины URL
- Необходимо обратить внимание на проблему с кодировкой URL
- Типы файлов, которые браузер может просматривать напрямую, недоступны для загрузки, например txt, png, jpg, gif и т. д.
- Вы не можете добавить заголовок и не можете аутентифицироваться
- Невозможно узнать ход загрузки
загрузка тега
мы знаем,a
Тег может получить доступ к адресу загруженного файла, а браузер помогает скачать. Однако для таких файлов, как txt, png, jpg и gif, которые браузеры поддерживают прямой просмотр, прямая загрузка недоступна (вы можете щелкнуть правой кнопкой мыши и сохранить как из меню).
Чтобы решить эту проблему прямого просмотра без загрузки, вы можете использоватьdownload
Атрибуты.
download
Атрибуты — это новые атрибуты в HTML5, и их совместимость можно понять ниже.can i use download
Общая совместимость очень хорошая, в основном можно разделить на IE и другой просмотр. Но есть несколько замечаний:
- Edge 13 аварийно завершает работу при попытке загрузить URL-адреса данных.
- Chrome 65 и более поздние версии поддерживают только ссылки для скачивания того же происхождения.
- Firefox поддерживает только ссылки для скачивания того же происхождения.
Исходя из приведенного выше описания, если вы попытаетесь загрузить междоменную ссылку, тоdownload
Эффект пропадет, если не установитьdownload
Стабильная производительность. То есть то, что браузер может предварительно просмотреть, все равно будет предварительно просмотрено, а не загружено.
Простое использование:
<a href="example.jpg" download>点击下载</a>
Вы можете взять значение атрибута, чтобы указать имя загруженного файла, то есть переименовать загруженный файл. Если не задано, по умолчанию используется исходное имя файла.
<a href="example.jpg" download="test">点击下载</a>
Как и выше, файл с именемtest
картинка
Следите за тем, поддерживается ли загрузка
Чтобы узнать, поддерживает ли браузерdownload
Атрибуты можно отличить по простому предложению кода
const isSupport = 'download' in document.createElement('a');
Для файлов, которые не могут быть загружены и доступны для просмотра в междоменном режиме, вы можете договориться с серверной частью, выполнить еще один уровень пересылки на внутреннем уровне и, наконец, вернуть ссылку на файл во внешний интерфейс в тот же домен, что и страница загрузки.
преимущество
- Может решить проблему невозможности напрямую загружать файлы, которые могут просматривать браузеры.
недостаток
- Знать адрес загружаемого файла
- Невозможно загрузить файлы, доступные для просмотра в браузере, в междоменном домене
- Имеет проблемы с совместимостью, особенно IE
- Не удается пройти аутентификацию
Использование объектов BLOB-объектов
Этот метод более прямой, чем предыдущий.a
Этикеткаdownload
Преимущество этого метода заключается в том, что в дополнение к использованию известного пути адреса файла для загрузки он также может получить файловый поток для загрузки, отправив API-запрос ajax. В конце концов, иногда серверная часть не предоставляет адрес загрузки для прямого доступа, а вызывает API.
использоватьBlob
объект может преобразовывать файловые потоки вBlob
бинарный объект. Этот объект совместим, следует отметить, что
- IE10 и ниже не поддерживаются.
- Доступ в браузере Safari
Blob Url
илиObject URL
в настоящее время имеет недостатки, как описано ниже черезURL.createObjectURL
сгенерированная ссылка.caniuse
На официальном сайте указано
Safari has a serious issue with blobs that are of the type application/octet-stream
Идея загрузки очень проста: отправьте запрос на получение бинарных данных и преобразование их вBlob
объект, использованиеURL.createObjectUrl
Сгенерировать URL-адрес, присвоить значение вa
помеченhref
свойства в сочетании сdownload
Скачать.
/**
* 下载文件
* @param {String} path - 下载地址/下载请求地址。
* @param {String} name - 下载文件的名字/重命名(考虑到兼容性问题,最好加上后缀名)
*/
downloadFile (path, name) {
const xhr = new XMLHttpRequest();
xhr.open('get', path);
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function () {
if (this.status === 200 || this.status === 304) {
// 如果是IE10及以上,不支持download属性,采用msSaveOrOpenBlob方法,但是IE10以下也不支持msSaveOrOpenBlob
if ('msSaveOrOpenBlob' in navigator) {
navigator.msSaveOrOpenBlob(this.response, name);
return;
}
// const blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') });
// const url = URL.createObjectURL(blob);
const url = URL.createObjectURL(this.response);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
};
}
Этот метод не может быть упущенa
помеченdownload
Установка свойств. Поскольку запрос передачи был установлен для типа возврата данныхBlob
тип(xhr.responseType = 'blob'
),такtarget.response
только одинBlob
объект, вы увидите два свойства, когда распечатаете егоsize
иtype
. Несмотря на то чтоtype
Свойство уже указывает тип файла, но на всякий случайdownload
В значении свойства указывается имя запроса, например Firefox не указывает загруженные файлы, не распознает тип.
Вы можете заметить, что в приведенном выше коде есть два комментария, на самом деле, помимо описанного выше метода записи, есть еще один метод записи, который немного изменился. Если не установлено при отправке запросаxhr.responseType = 'blob'
, запрос ajax по умолчанию вернетDOMString
тип данных, то есть строка. В настоящее время необходимы два прокомментированных кода, и возвращаемый текст преобразуется вBlob
объект, а затем создайте URL-адрес большого двоичного объекта, вам нужно закомментировать исходныйconst url = URL.createObjectURL(target.response)
.
преимущество
- Может решить проблему невозможности напрямую загружать файлы, которые могут просматривать браузеры.
- Заголовок может быть установлен, а также может быть добавлена информация для аутентификации.
недостаток
- Проблемы совместимости, недоступные ниже IE10; браузер Safari может обратить внимание на использование
Использовать base64
Использование здесь такое же, как и вышеBlob
В основном та же идея, единственное отличие состоит в том, что выше используетсяBlob
генерация объектаBlob URL
, и здесь генерируетсяData URL
, так называемыйData URL
,этоbase64
Закодированная форма URL.
/**
* 下载文件
* @param {String} path - 下载地址/下载请求地址。
* @param {String} name - 下载文件的名字(考虑到兼容性问题,最好加上后缀名)
*/
downloadFile (path, name) {
const xhr = new XMLHttpRequest();
xhr.open('get', path);
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function () {
if (this.status === 200 || this.status === 304) {
const fileReader = new FileReader();
fileReader.readAsDataURL(this.response);
fileReader.onload = function () {
const a = document.createElement('a');
a.style.display = 'none';
a.href = this.result;
a.download = name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
}
};
}
преимущество
- Может решить проблему невозможности напрямую загружать файлы, которые могут просматривать браузеры.
- Заголовок может быть установлен, а также может быть добавлена информация для аутентификации.
недостаток
- Проблемы совместимости, недоступные ниже IE10
Об именах файлов
Иногда мы не знаем имя файла перед отправкой запроса на загрузку, или имя файла предоставляется бэкендом, и нам нужно найти способ его получить.
Content-Disposition
При возврате файлового потока мы наблюдаем информацию, возвращаемую интерфейсом в браузере, и увидим такой заголовок:Content-Disposition
Content-Disposition: attachment; filename=CMCoWork__________20200323151823_190342.xlsx; filename*=UTF-8''CMCoWork_%E4
Приведенные выше значения являются примерами.
Он содержит имя файла, мы можем найти способ получить в нем имя файла. Мы видим, что естьfilename=
иfilename*=
, последний не обязательно имеет, в старых браузерах или отдельных браузерах эта форма не будет поддерживаться,filename*
УсыновленныйRFC 5987
Метод кодирования, указанный в .
Итак, если вы хотите получить имя файла, это делается, перехватите два значения поля в этой строке.
Глядя на приведенный выше пример, вы можете обнаружить, что это странно. Да, если имя на английском, то с ним легко обращаться, если с китайскими или другими спецсимволами, то надо разбираться с кодировкой.
-
filename
, серверной части необходимо обработать форму кодирования, но даже если серверная часть обрабатывается, ситуация синтаксического анализа будет отличаться в зависимости от каждого браузера. С ним трудно иметь дело, поэтому он позадиfilename*
-
filename*
, поддерживается современными браузерами, чтобы решитьfilename
недостатки, как правилоUTF-8
,мы используемdecodeURIComponent
Его можно расшифровать и восстановить первоначальный вид. Конечно, перед декодированием вы вводите значениеUTF-8''
Эта часть удалена.
Итак, прежде чем мы это осознаем, нам нужно понять, что принятиеContent-Disposition
Содержимое не на 100% соответствует вашим ожиданиям, если только ваше имя файла не состоит из английских цифр.
Мы извлекаем значение имени файла:
// xhr是XMLHttpRequest对象
const content = xhr.getResponseHeader('content-disposition'); // 注意是全小写,自定义的header也是全小写
if (content) {
let name1 = content.match(/filename=(.*);/)[1]; // 获取filename的值
let name2 = content.match(/filename\*=(.*)/)[1]; // 获取filename*的值
name1 = decodeURIComponent(name1);
name2 = decodeURIComponent(name2.substring(6)); // 这个下标6就是UTF-8''
}
Выше мы получаем два имени файлаname1,name2
, если оба существуют, то приоритет отдаетсяname2
Да потому что так надежнее,name1
Если оно содержит китайские или специальные символы, существует риск невозможности восстановления настоящего имени файла.
дефект
- Имя файла, которое не является полностью цифровым на английском языке, если его поддерживает только браузер.
filename
, полученная кодировка имени файла может быть проблематичной.
настраиваемый заголовок
По сути то же, что и вышеContent-Disposition
Почти, но здесь мы не используем заголовок по умолчанию, мы настраиваем его сами.response header
, решить метод кодирования с помощью серверной части и вернуться, клиентская часть напрямую получает этот настраиваемый заголовок, а затем использует соответствующее декодирование, например, с помощьюdecodeURIComponent
.
Но всем нам нужно знать, что в случае кроссдоменности заголовок, полученный внешним интерфейсом, имеет только 6 базовых полей по умолчанию:Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
,Pragma
.
Поэтому, если вы хотите получить другие заголовки, вам нужно сотрудничество с серверной частью, установите
Access-Control-Expose-Headers: Content-Disposition, custom-header
Таким образом, интерфейс может получить поле заголовка, соответствующее выставленному, необходимо обратить на это внимание.Content-Disposition
тоже нужно выставить.
Переименовать
Вот дополнительный метод, функция этого метода состоит в том, чтобы получить имя суффикса, когда вы знаете полное имя файла (включая имя суффикса) и хотите его переименовать, но имя суффикса то же самое.
function findType (name) {
const index = name.lastIndexOf('.');
return name.substring(index + 1);
}
Пожалуйста, не воспроизводите без разрешения
Мой публичный аккаунт WeChat