故事背景
我最近接到个需求,由于一些原因,需要将原本是后端进行处理的素材打包下载,需要改为由前端处理。具体功能如下:
- 选单个素材,直接下载,xxx.jpg
- 选多个素材,将多个素材进行zip打包并下载,xxx.zip
在开发中就遇到一些问题啦
- 使用
a标签
,图片、视频是直接打开预览而不是下载? - 使用
a标签
,使用download也无法直接下载? - 线上资源跨域,无法下载问题?
- 使用
jszip
进行zip打包下载,为什么无法直接通过url直接下载,需要先fetch下载,再zip打包? - 为什么浏览器没有展示下载进度?
以此为由进行前端文件下载的总结,从前置分析、下载方式、问题分析方面进行梳理。
前言
作为前端开发工程师,在系统开发过程中,避免不了需要进行各种类型文件等下载。不知道你有没有遇到过呢?还有没有存疑的地方?现在就一次性将下载遇到的问题与方法讲清楚,遇到对文件下载不再迷茫。
无论你是实习/初/中/高/资深级前端,希望这篇文章能有一点价值,能带给你一点帮助。
文件下载分析
对于文件的下载,需要确认从文件类型、文件路径、是否会跨域三个方面进行判断,不同的类型、文件、是否跨域,前端的下载方式不同。
- 文件类型:要下载的文件的类型
- 文件路径:要下载的文件的来源路径,是本地资源、还是链接地址、接口请求返回的二进制流文件
- 跨域问题:资源是否跨域,是否允许前端跨域请求资源
文件类型
我们在进行文件下载功能的处理时,首先要确认下载的内容即文件类型是什么?对于不同的文件类型,前端进行下载的方式可能会有所不同。
常见的前端下载文件类型:
- 图片类型(例如jpeg、png、gif等)
- 视频类型(例如mp4、avi、mov等)
- 音频类型(例如mp3、wav等)
- 文本类型(例如txt、html、xml、json等)
- 压缩文件类型(例如zip、rar等)
- 文档类型(例如doc、pdf等)
- 表格类型(例如xlsx、csv等)
- 数据库文件类型(例如sql、db等)
- 代码文件类型(例如js、css等)
- 资源文件类型(例如svg、icon等)
实际上还有很多其他类型的文件可以通过前端进行下载:
- .woff 网页上渲染自定义字体的文件格式。
- .ics 日历事件和任务的文件格式。
- .gpx 全球定位系统(GPS)数据的文件格式。
- .md 编写文档的标记语言。
- .dwg 常用于CAD软件中,适合存储2D和3D模型数据。
- .stl 三维打印的文件格式,用于存储三维模型的几何数据。
前端文件下载方式
a标签下载
可以利用a标签
的超链接进行下载,该方式能下载的文件类型是浏览器不能直接预览的类型,如.doc、.xlsx、.zip等
而有些类型,使用a标签
是直接打开预览。浏览器支持直接预览的文件类型包括:
- 文本文件:.txt、.html、.xml、.css、.js、.json、.csv等;
- 图片文件:.jpg、.png、.gif、.bmp、.ico等;
- 音频文件:.mp3、.wav、.ogg、.flac等;
- 视频文件:.mp4、.mov、.avi、.wmv等;
- PDF文件:.pdf。
a标签下载的基本使用
html
<a href="test.xlsx">Excel文件</a> <!-- 执行下载 -->
<a href="test.png">图片</a> <!-- 浏览器打开图片-->
<!-- href的链接可以是本地也可以完整路径,路径是否跨域没有关系,跟文件类型有关。如果是浏览器不能预览的类型,无论是否跨域都能下载,如果是浏览器能预览的类型,则都是浏览器打开 -->
<a href="https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv">视频1</a> <!-- 执行下载-->
<a href="https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4">视频2</a> <!-- .mp4类型,浏览器打开视频-->
a标签下载的download使用
对于浏览器支持直接预览的文件类型,使用download
属性,解决点击直接打开预览而不是下载的问题,还可以使用download='名称.类型'
对要下载的文件重命名
html
<a href="test.png" download>图片下载</a>
<a href="test.png" download="测试.png">图片下载</a> <!-- 重命名 -->
注意⚠️⚠️:
download
只在同源 (域名、协议、端口号一致)下有效,如果href的链接是跨域的,那么即使加了download
,也还是打开预览。所以在开发中对于下载的文件要注意下载链接的问题
html
<!--如果是浏览器不能预览的类型,无论是否跨域都能下载,如果是浏览器能预览的类型,此时就要判断是否同域名 -->
// 如当前域名是:http://aa.com/frontend
<a href="http://aa.com/frontend/test.png" download>图片</a> <!--点击是下载 -->
<a href="https://aa.com/frontend/test.png" download>图片</a> <!--点击是预览-->
动态的创建 a 标签
js
const downloadFile = (url, fileName='') => {
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
window.open或location.href方式
使用 window.open
和 location.href
跟 a标签
一样,能下载浏览器不能直接预览的类型,对于浏览器支持直接预览的文件类型,同样是打开预览。
js
window.open("test.xlsx") // 执行下载
window.open("test.png") // 浏览器打开图片
js
location.href = 'test.xlsx';// 执行下载
location.href = 'test.png';// 浏览器打开图片
注意⚠️⚠️: 这两种方式无法对文件进行重命名
Blob对象方式
使用 URL.createObjectURL
方法
Blob
对象方式,即使用URL.createObjectUrl(object)
方法【object值为File
对象、Blob
对象或者 MediaSource
对象】,同步处理会生成url地址,之后就可以将url地址赋值在a标签
的href
属性上,结合download
进行下载。
一般在需要请求的后端获取下载内容的情况,或者资源跨域需要请求回来再下载的情况。下面的以结合aixos
为例:资源(跨域资源且资源允许跨域请求)
js
import axios from "axios";
// 资源路径
const path = "https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4"
//发送请求,设置返回对象为Blob
const res = await axios.get(path, { responseType: "blob" });
//将返回值通过URL.createObjectURL转成blob:http://xxx的地址
const url = window.URL.createObjectURL(res.data);
// 利用a标签进资源下载
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.download = '文件名';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
base64方式
使用new FileReader()
与 reader.readAsDataURL(res)
跟Blob的对象方式差不多,即使用new FileReader()
与 reader.readAsDataURL(res)
方法,需要回调异步执行,会生成base64编码的字符串,之后就可以将url地址赋值在a标签
的href
属性上,结合download
进行下载。
js
import axios from "axios";
// 资源路径
const path = "https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4"
//发送请求,设置返回对象为Blob
const res = await axios.get(fileUrl, { responseType: "blob" });
const reader = new FileReader();
// 传入被读取的blob对象
reader.readAsDataURL(res.data);
// 读取完成的回调事件
reader.onload = () => {
// 生成的base64编码
const url = reader.result;
// 利用a标签进资源下载
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.download = '文件名';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
canvas方式
对于图片的下载,我们总结一下:文件类型是图片 ,资源链接同源 情况,使用a标签download
实现下载。
问题来了,文件类型是图片 ,资源链接跨域,且后端不允许跨域读取,如何处理解决?
我们使用canvas
,通过toDataURL
方法将canvas对象转换为base64位编码
js
download(link, picName) {
const img = new Image();
img.src = link
// 前端对图片的跨域处理
img.setAttribute("crossOrigin", "Anonymous");
// 读取完成的回调事件
img.onload = function () {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0, img.width, img.height);
// 生成的base64编码
const url = canvas.toDataURL("images/png");
// 利用a标签进资源下载
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.download = '文件名';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
}
response-content-type=application/octet-stream方式❓
与canvas相似
对于图片的下载,我们总结一下:文件类型是图片 ,资源链接同源 情况,使用a标签download
实现下载。
问题来了,文件类型是图片 ,资源链接跨域,使用download无效情况下,如何处理解决?
有一种方式是在资源链接后加?response-content-type=application/octet-stream
。
application/octet-stream
是应用程序文件的默认值。意思是未知的应用程序文件 ,浏览器一般不会自动执行或询问执行。浏览器会像对待,设置了HTTP头Content-Disposition:attachment
的文件一样来对待这类文件,即浏览器触发下载行为。
html
<!-- 浏览器打开图片-->
<a href="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" download>图片</a>
<!-- 执行下载-->
<a href="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg?response-content-type=application/octet-stream" download>图片</a>
疑问❓❓ 经过我自己的测试,发现并不是所有的图片跨域资源链接加上这个都有用,找了一些文章看,说是跟MIME类型有关,目前我还不是很理解,留个疑问在这里。能下载的图片是可以将content-type设置上application/octet-stream,如下图
不常见文件类型的文件下载
对于一些不常见的文件类型,如果浏览器不能自动识别这些文件类型并下载,可以通过使用第三方库或工具来进行下载。
如:Markdown文件(.md),前端可以通过以下两种方式进行下载:
- 使用FileSaver.js,这是一个用于将文件保存到本地的JavaScript库。通过引入这个库,可以使用saveAs()函数将Markdown文件保存为本地文件。而且,由于Markdown文件可以直接被浏览器显示,前端也可以使用Markdown解析器将Markdown文件渲染成HTML格式之后再进行下载。
- 将Markdown文件转换为Word文档或PDF文档进行下载。可以使用第三方的Markdown转Word或Markdown转PDF工具(例如Pandoc),将Markdown文件转换为Word或PDF格式的文件,然后将这些文件下载到本地。可以通过Ajax请求服务端接口来调用这些工具进行文件转换,然后通过浏览器下载链接将转换后的文件下载到本地。
最后
本篇就先将文件下载总结清楚,关于文件打包下载,我们下文再继续。