美文网首页
前端文件下载方法

前端文件下载方法

作者: 懂会悟 | 来源:发表于2023-03-16 16:37 被阅读0次

前言

在前端站点上下载文件,这是一个极其普遍的需求,很早前就已经有各种解决方法了,为什么还写这么老的文章,只是最近在带一个新人,他似乎很多都一知半解,也遇到了我们必经问题之“不能下载txt、png等文件”的典型问题,我就给他总结下下载的几个方式。顺便分享出来,也许,真有人需要。

方法一: form表单提交

这是以前常使用的传统方式,毕竟那个年代,没那么多好用的新特性呀。

道理也很简单,为一个下载按钮添加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等)

方法二: open或location.href

最简单最直接的方式,实际上跟a标签访问下载链接一样

window.open('downloadFile.zip');

location.href = 'downloadFile.zip';

当然地址也可以是接口api的地址,而不单纯是个链接地址。

优点
  • 简单方便直接
缺点
  • 会出现URL长度限制问题
  • 需要注意url编码问题
  • 浏览器可直接浏览的文件类型是不提供下载的,如txt、png、jpg、gif、pdf等
  • 不能添加header,也就不能进行鉴权
  • 无法知道下载的进度

方法三:a标签的download

我们知道,a标签可以访问下载文件的地址,浏览器帮助进行下载。但是对于浏览器支持直接浏览的txt、png、jpg、gif等文件,是不提供直接下载(可右击从菜单里另存为)的。

为了解决这个直接浏览不下载的问题,可以利用download属性。

download属性是HTML5新增的属性,兼容性可以了解下 can i use download

总体兼容性算是很好了,基本可以区分为IE和其他浏览器。但是需要注意一些信息:

  • Edge 13在尝试下载data url链接时会崩溃。
  • Chrome 65及以上版本只支持同源下载链接。
  • Firefox只支持同源下载链接。

基于上面描述,如果你尝试下载跨域链接,那么其实download的效果就会没了,跟不设置download表现一致。即浏览器能预览的还是会预览,而不是下载。

简单用法:

<a href="example.jpg" download>点击下载</a>

可以带上属性值,指定下载的文件名,即重命名下载文件。不设置的话默认是文件原本名。

<a href="example.jpg" download="test">点击下载</a>

如上,会下载一个名叫test的图片

监测是否支持download
要知道浏览器是否支持download属性,简单的一句代码即可区分

const isSupport = 'download' in document.createElement('a');

对于在跨域下不能下载可浏览的文件,其实可以跟后端协商好,在后端层做多一层转发,最终返回给前端的文件链接跟下载页同域就好了。

优点
  • 能解决不能直接下载浏览器可浏览的文件
缺点
  • 得已知下载文件地址
  • 不能下载跨域下的浏览器可浏览的文件
  • 有兼容性问题,特别是IE
  • 不能进行鉴权

方法四 利用Blob对象

该方法较上面的直接使用a标签download这种方法的优势在于,它除了能利用已知文件地址路径进行下载外,还能通过发送ajax请求api获取文件流进行下载。毕竟有些时候,后端不会直接提供一个下载地址给你直接访问,而是要调取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对象,然后创建blob url,此时需要注释掉原本的const url = URL.createObjectURL(target.response)。

优点
  • 能解决不能直接下载浏览器可浏览的文件
  • 可设置header,也就可添加鉴权信息
缺点
  • 兼容性问题,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);
            };
        }
    };
}
优点
  • 能解决不能直接下载浏览器可浏览的文件
  • 可设置header,也就可添加鉴权信息
缺点
  • 兼容性问题,IE10以下不可用

关于文件名

有时候我们在发送下载请求之前,并不知道文件名,或者文件名是后端提供的,我们就要想办法获取。

Content-Disposition

当返回文件流的时候,我们在浏览器上观察接口返回的信息,会看到有这么一个header: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的内容,并不是百分百能符合你预期的,除非你的文件名全是英文数字。

我们提取文件名值:

// 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,获取的文件名编码可能会有问题。

自定义header

本质上跟上述的Content-Disposition差不多,只是我们这里不使用默认的header,我们自己自定义一个response header,跟后端决定好编码方式返回,前端直接获取这个自定义header,然后使用对应的解码即可,如使用decodeURIComponent。
但是我们都要知道,在跨域的情况下,前端获取到的header只有默认的6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
所以你想要获取到别的header,需要后端配合,设置

Access-Control-Expose-Headers: Content-Disposition, custom-header

这样,前端就能获取到对应暴露的header字段,需要注意的是,Content-Disposition也是需要暴露的。

重命名

这里额外提供个方法,该方法作用是,当你知道文件的全名(含后缀名),想要重命名,但是得后缀名一样,来获取后缀名。

function findType (name) {
    const index = name.lastIndexOf('.');
    return name.substring(index + 1);
}

相关文章

  • Web前端下载图片/文件

    Web前端下载图片/文件 条条道路通罗马,这里只记录a标签下载图片/文件的方法。 文件类型MIME Blob 对象...

  • Android文件下载前获取多个网络文件总大小

    文件下载是开发中比较常见的功能,当后端无法告诉前端下载文件大小时,就需要前端自己获取文件大小 简单的方法为: 当文...

  • 前端下载文件成功,但打不开

    前端通过后端返回的文件流下载excel文件方法如下: 但是我遇到一个问题:就是下载文件成功了但是打不开,一看是文件...

  • js 文件保存下载

    ajax下载文件下载文件最简单的是用get方法配合前端的a标签,但是如果是post方法或者是请求需要加头信息就需要...

  • 文件上传与下载

    文件上传 前端页面 Action处理类 struts.xml 文件下载 前端页面 处理下载请求的action st...

  • 前端文件下载方式

    前端对于文件下载这块的一些常用的基本方法。 一 通过a标签来下载文件 在html5中 a 标签多了一个属性...

  • js blob导出文件 文件下载 中文乱码的问题

    需求:后端文件以二进制流的形式返回给前端, 前端需要读取流文件实现文件下载。 场景:下载成功,文件乱码。 原因:与...

  • 前端文件下载

    最近做项目遇到要把文件放在前端项目中,然后点击下载完整代码: 先把文件放在静态目录src/assets里面 通过i...

  • Vue-纯前端导出word文档

    在项目中,我们可以借助后端返回文件流实现文件下载。如果前端有数据,也可以借助前端框架进行下载。本文将介绍如何在前端...

  • Vue-纯前端导出word文档

    在项目中,我们可以借助后端返回文件流实现文件下载。如果前端有数据,也可以借助前端框架进行下载。本文将介绍如何在前端...

网友评论

      本文标题:前端文件下载方法

      本文链接:https://www.haomeiwen.com/subject/scooortx.html