предисловие
Загрузка файлов, развернутых на сервере, по щелчку мыши является обычным требованием стороны B, но для получения хорошего опыта есть еще много небольших знаний, которые стоит изучить. Недавно автор разрабатывает персональный облачный диск, и я также столкнулся с этой областью в процессе разработки функции загрузки файлов (после завершения я также опубликую сообщение в блоге, поэтому сначала возьму яму). В Интернете есть различные реализации, но также есть различные проблемы, такие как не поддерживается междоменная загрузка, вам нужно открыть вкладку браузера или изображения и другой контент, который может быть распознан браузером напрямую, и т. д. Подробности, которые повлиять на опыт, здесь Просто разложить и поговорить.
общий метод
встроенная загрузка тега
Реализация очень проста:
<a href='下载文件的url'>点我下载</a>
Я считаю, что это первый метод, о котором думает большинство людей.Обычно опыт идеален, но если вы столкнетесь с загруженными изображениями, pdf, txt и другими файлами, которые браузер может напрямую анализировать и отображать, появятся следующие результаты:
После того, как браузер видит этот ресурс, может быть разрешен, он будет прыгать непосредственно на ресурсы URL-URL и показать окно. Этот опыт должен был заключаться в том, чтобы большинство людей не могли принять, для чего мы можем добавить
download
Параметр сообщает браузеру, что мы хотим загрузить этот ресурс, например:
<a href='下载文件的url' download>点我下载</a>
пройти черезdownload
Вы также можете изменить имя и тип загруженного файла:
<a href='下载文件的url' download='文件名.后缀名'>点我下载</a>
Если изменение типа файла не требуется, суффикс имени можно опустить:
<a href='下载文件的url' download='文件名'>点我下载</a>
download属性
Это серебряная пуля? Извините, что разочаровал васchrome
далее, если ресурс не гомологичен,download
Атрибут недействителен, и это то же самое, что добавить или нет.Если вы хотите решить эту проблему, вы можете сотрудничать только с бэкэндом. Что касается содержания совместимости этого атрибута, есть итоговый пост Чжан Синьсюй.Понимание атрибута загрузки в HTML/HTML5Он может быть использован для справки и не будет повторяться здесь.
window.open открывает новую вкладку скачать
Этот метод проходитwinodw.open
Откройте новую вкладку и используйте ресурсы, которые не могут быть проанализированы браузером, чтобы стать функцией загрузки для достижения этой функции, и API не сложный:
window.open('目标url')
Посмотрим на производительность под обычными файлами:
Хорошо видно, что здесь происходит процесс открытия вкладки. Если браузер обнаружит, что ресурс не может быть проанализирован, браузер закроет вкладку и продолжит процесс загрузки.
К сожалению, все должны больше беспокоиться о производительности фотографий.
a
Загрузка тегов также неудовлетворительна:Для содержимого, которое браузер может анализировать напрямую, браузер откроет вкладку и отобразит содержимое.
отправив форму
В принципе, строя форму,submit
способ запроса ресурсов с сервера:
export function downloadUrlFile(url) {
let tempForm = document.createElement('form')
tempForm.action = url
tempForm.method = 'get'
tempForm.style.display = 'none'
document.body.appendChild(tempForm)
tempForm.submit()
return tempForm
}
Датьform
Настройки формыget
метод, а затем запрашивать ресурсы с сервера в соответствии с входящим URL-адресом. Согласно предыдущему процессу, давайте посмотрим на работу с обычными файлами:
Опыт хороший, и вкладка не мерцает. Далее давайте посмотрим на опыт для картинок:
К сожалению, для таких файлов, как изображения, вкладка по-прежнему будет открыта для непосредственного отображения содержимого.
Надежда всей деревни: download.js
Это скрипт написанный зарубежным боссом специально для скачивания файлов.У него очень богатые функции.Он может не только скачивать контент на сервер,но и таргетироватьbase64
Ожидание загрузки файла в виде dataUrl, метод использования очень богат, вот порталскачать официальную документацию.js, вставьте исходный код и простые комментарии автора сюда (обратите внимание, что это отображается в виде встроенного скрипта html, при необходимости его можно вытащить и сделать в модуль для внешнего ознакомления):
<script>//download.js v4.2, by dandavis; 2008-2016. [CCBY2] see http://danml.com/download.html for tests/usage
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
// v4 adds AMD/UMD, commonJS, and plain browser support
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
// v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
// https://github.com/rndme/download
(function (root, factory) {
// 兼容各种模块写法,在全局对象上挂载download方法
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
// 针对AMD规范,注册一个匿名模块
define([], factory);
} else if (typeof exports === 'object') {
// 针对Node,环境,不支持严格模式
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// 浏览器全局变量支持
// Browser globals (root is window)
root.download = factory();
}
}(this, function () {
// 第一个参数是数据,第二个参数是文件名,第三个参数是mime类型
// 下载服务器上的文件直接第一个参数传入url即可,后两个不用传
return function download(data, strFileName, strMimeType) {
// 这里的脚本仅支持客户端
var self = window, // this script is only for browsers anyway...
// 默认的mime类型
defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
mimeType = strMimeType || defaultMime,
payload = data,
// 如果只传入第一个参数,则把其解析为下载url
url = !strFileName && !strMimeType && payload,
// 创建a标签,方便下载
anchor = document.createElement("a"),
toString = function(a){return String(a);},
// 根据浏览器兼容性,提取Blob
myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
fileName = strFileName || "download",
blob,
reader;
myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
// 调换参数的顺序,允许download.bind(true, "text/xml", "export.xml")这种写法
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
payload=[payload, mimeType];
mimeType=payload[0];
payload=payload[1];
}
// 根据传入的url这一个参数下载文件(必须是同源的,因为走的是XMLHttpRequest)
if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
// 解析出文件名
fileName = url.split("/").pop().split("?")[0];
// 设置a标签的href
anchor.href = url; // assign href prop to temp anchor
// 避免链接不可用
if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
// 构造一个XMLHttpRequest请求
var ajax=new XMLHttpRequest();
// 设置get方法
ajax.open( "GET", url, true);
// 设置响应类型为blob,避免浏览器直接解析出来并展示
ajax.responseType = 'blob';
// 设置回调
ajax.onload= function(e){
// 再次调用自身,相当于递归,把xhr返回的blob数据生成对应的文件
download(e.target.response, fileName, defaultMime);
};
// 发送ajax请求
setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
return ajax;
} // end if valid url?
} // end if url?
//go ahead and download dataURLs right away
// 如果是dataUrl,则生成文件
if(/^data\:[\w+\-]+\/[\w+\-]+[,;]/.test(payload)){
// 如果满足条件(大于2m,且myBlob !== toString),直接通过dataUrlToBlob生成文件
if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
payload=dataUrlToBlob(payload);
mimeType=payload.type || defaultMime;
}else{
// 如果是ie,走navigator.msSaveBlob
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
// 否则走saver方法
saver(payload) ; // everyone else can save dataURLs un-processed
}
}//end if dataURL passed?
blob = payload instanceof myBlob ?
payload :
new myBlob([payload], {type: mimeType}) ;
// 根据传入的dataurl,通过myBlob生成文件
function dataUrlToBlob(strUrl) {
var parts= strUrl.split(/[:;,]/),
type= parts[1],
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
binData= decoder( parts.pop() ),
mx= binData.length,
i= 0,
uiArr= new Uint8Array(mx);
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
return new myBlob([uiArr], {type: type});
}
// winMode 是否是在window上调用
function saver(url, winMode){
// 如果支持download标签,通过a标签的download来下载
if ('download' in anchor) { //html5 A[download]
anchor.href = url;
anchor.setAttribute("download", fileName);
anchor.className = "download-js-link";
anchor.innerHTML = "downloading...";
anchor.style.display = "none";
document.body.appendChild(anchor);
setTimeout(function() {
// 模拟点击下载
anchor.click();
document.body.removeChild(anchor);
// 如果在window下,还需要解除url跟文件的链接
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
}, 66);
return true;
}
// handle non-a[download] safari as best we can:
// 针对不支持download的safari浏览器,走window.open的降级操作,优化体验
if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
url=url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
if(!window.open(url)){ // popup blocked, offer direct download:
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
}
return true;
}
//do iframe dataURL download (old ch+FF):
// 针对老的chrome或者firefox浏览器,创建iframe,通过设置iframe的url来达成下载的目的
var f = document.createElement("iframe");
document.body.appendChild(f);
if(!winMode){ // force a mime that will download:
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
}
f.src=url;
// 移除工具节点
setTimeout(function(){ document.body.removeChild(f); }, 333);
}//end saver
// 针对ie10+ 走浏览器自带的msSaveBlob
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
return navigator.msSaveBlob(blob, fileName);
}
// 如果全局对象下支持URL方法
if(self.URL){ // simple fast and modern way using Blob and URL:
// 根据blob创建指向文件的ObjectURL
saver(self.URL.createObjectURL(blob), true);
}else{
// handle non-Blob()+non-URL browsers:
// 针对不支持Blob和URL的浏览器,通过给saver传入dataUrl来保存文件
if(typeof blob === "string" || blob.constructor===toString ){
try{
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
}catch(y){
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
}
}
// Blob but not URL support:
// 支持Blob但是不支持URL方法的浏览器,通过构造文件阅读器来保存文件
reader=new FileReader();
reader.onload=function(e){
saver(this.result);
};
reader.readAsDataURL(blob);
}
return true;
}; /* end download() */
}));</script>
Анализируя исходный код, мы можем обнаружить, чтоdownload.js
Это кульминация тех методов, упомянутых ранее. Чтобы решить проблему автоматического открытия наиболее надоедливых картинок, скрипт создает запрос xhr и меняет тип ответа наblob
, Сделать браузер нераспознаваемым, чтобы избежать прямого открытия, а затем заново собрать загруженный файл блоба в нужный нам файл. Для совместимости с разными браузерами используйтеa
вкладка скачать,window.open
и другие методы в качестве схемы понижения. Давайте сначала испытаем эту бутылку панацеи:
Загрузка изображений проходит гладко. Но проблема еще не решена, обратите внимание на официальное предложение:
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
Что это означает?Когда URL-адрес передается в качестве параметра, поддерживаются только ресурсы одного происхождения или сервер настроен с помощью CORS для поддержки междоменного доступа (поскольку используется запрос xhr). Протестировать и проверить:
В результате браузер сообщил о междоменной ошибке (объясняется здесь, автор загрузил файл msi в облачное хранилище апплета и получил ссылку для скачивания. Фабрика гуся установила разные правила междоменного доступа для конфиденциальных файлов, таких как msi и exe, в результате чего эта часть файла будет перехвачена при загрузке). В этом случае вы можете использовать такие методы, как построение отправки формы, чтобы получить идеальный опыт без междоменных ограничений.
Суммировать
Сравнив приведенные выше методы загрузки файлов на стороне браузера, мы обнаружили, чтоa标签
Опыт загрузки наилучший, для изображений и других ресурсов используйте тот же источникdownload
свойства могут получить лучшие результаты,window.open
Откроется новая вкладка, и экран подпрыгнет. За исключением плохого опыта файлов, которые могут быть непосредственно проанализированы браузерами, нет никаких недостатков в использовании представления формы конструкции.Вышеуказанные три метода не подпадают под междоменные ограничения (ограничение здесь означает, что междоменные ресурсы могут быть загружены , независимо от опыта).download.js
За исключением междоменной ошибки, сообщаемой при загрузке междоменных ресурсов, в принципе нет недостатка.В сочетании с любым из предыдущих методов можно собрать более совершенное решение. Вот пример из моего проекта:
async function downloadFile() {
fileList.filter(item => chekcList.findIndex(sitem => item._id === sitem) >= 0)
.map(item => item.downloadUrl).map(item => {
// download是download.js抽出来的函数
const res = download(item);
if (res !== true) {
// 跨域错误无法捕获,如果返回不是true的话就走另外一个方法
// downloadUrlFile是简单封装的通过提交表单下载文件的方法
downloadUrlFile(item)
}
});
}
Сравнение четырех методов:
метод | Междоменная поддержка | Всплывающая ли вкладка | Поддерживать ли прямую загрузку ресурсов, которые могут отображаться браузером |
---|---|---|---|
этикетка | да | нет | Гомологичный черезdownload поддержка атрибутов |
window.open | да | да | нет |
Построить форму отправки | да | нет | нет |
download.js | нет | нет | да |
Справочные ресурсы
Понимание атрибута загрузки в HTML/HTML5
скачать официальную документацию.js