完美的兼容性好的前端文件下载方式探讨

737 阅读4分钟

在项目中,经常会遇到文件下载的需求。一开始,都是copy既有项目的下载方法,用于自己的项目。但是,渐渐发现,copy过来的方法,有时会出现这样那样的问题,而且不同的项目,用的方法又是不一样的,没有一个通用的方法。

故感觉自己在文件下载这块,思绪还是乱的,没有一个清晰且系统概念。所以想通过本篇文章,好好地整理一下。

我们下载的文件,通常有三种形式:

  • 有明确地址路径的文件
  • 二进制数据文件
  • base64文件

而根据浏览器的特性,又可以分为:

  • 浏览器可直接浏览的文件,如txt、png、jpg、gif等格式的文件
  • 浏览器不能直接浏览的文件,如doc、excel、zip等格式的文件

在下载方式这块,针对不同文件类型,常用的方式也不同。

一、对于有明确地址路径的文件进行下载

  1. window API 实现下载
window.open(fileUrl)
window.location.href = fileUrl

这两种方式虽然简单方便直接,但是浏览器能直接打开的文件只能预览,不能下载。

  1. form表单实现下载
const fromObj = document.createElement('form')
fromObj.action = fileUrl
formObj.method = 'get'
formObj.style.display = 'none'
const formItem = document.createElement('input')
formItem.value = fileName
formItem.name = 'fileName'
formObj.appendChild(formItem)
document.body.appendChild(formObj)
formObj.submit()
document.body.removeChild(formObj)

这是以前常用的传统方式,利用表单提交的功能来实现文件的下载。兼容性好,但是也无法下载浏览器能直接预览的文件。

  1. a标签实现下载
<a href="fileUrl"></a>

如果仅仅这样写,对于浏览器能直接打开的文件也是只能预览,不能下载。

要想能够直接下载浏览器能直接打开的文件,可以利用download属性。

<a href="fileUrl" download="fileName"></a>

但download属性实现浏览器可预览文件下载也有限制:

  • 同源,即所要下载的文件与当前页面同源。
  • 非IE浏览器。

也就是说,如果不同源或是IE浏览器,即使加了download属性,也不能实现浏览器可预览文件的下载,且自定义文件名也不会生效。

因为当a标签的下载链接跨域时,download属性将不会生效,原因是浏览器无法获取到文件,不能对他进行更改。

所以,在这种情况下,如果你是下载浏览器无法预览的文件,那么浏览器也会自动下载,但是如果你使用浏览器可以预览的文件,那么浏览器就会采取预览模式,无法直接下载。

4.url转blob再利用a标签实现下载

先封装一个url转blob方法,再封装一个下载方法,具体如下:

  function getBlob(fileUrl) {
     return new Promise(resolve => {
          const xhr = new XMLHttpRequest()
          xhr.open('GET', fileUrl, true)
          xhr.responseType = 'blob'// 请求类型是blob类型
          xhr.crossOrigin = '*' // 解决跨域问题
          xhr.onload = () => {
          if (xhr.status === 200) {
            resolve(xhr.response)
          }
          }
          xhr.send()
     })
   },
   
   function saveAs(blob, filename) {
        if (window.navigator.msSaveOrOpenBlob) {
          navigator.msSaveBlob(blob, filename)
        } else {
          const link = document.createElement('a')
          const body = document.querySelector('body')
          link.href = window.URL.createObjectURL(blob)
          link.download = filename // 修改文件名
          link.style.display = 'none'
          body.appendChild(link)
          link.click()
          body.removeChild(link)
          window.URL.revokeObjectURL(link.href)
        }
      },

再调用以上封装的方法:

const fileUrl = '文件地址链接'
const fileName = '文件名'
 getBlob(fileUrl).then(res => { 
      saveAs(res, fileName) 
 })

此种方式,虽然加了 xhr.crossOrigin = '*'来解决跨域问题,但是在实际项目应用中,也要注意fileUrl与系统Url是否同源的问题。

二、对于二进制数据文件进行下载

downFile(params).then(res=> {
 
      const fileName = res.headers['content-disposition'].split('=')[1];
 
      const data = res.data;
 
      const blob = new Blob([data]);
      
      //如果浏览器不支持download属性(也就是使用IE10及以上的时候,使用msSaveOrOpenBlob方法,但IE10以下也不支持msSaveOrOpenBlob
      
      if(window.navigator.msSaveOrOpenBlob) {
      
        window.navigator.msSaveOrOpenBlob(blob, fileName )
        return
      }
      //
      const a = document.createElement('a');
 
      const href = window.URL.createObjectURL(blob); // 创建下载的链接
 
      a.href = href;
 
      a.download = decodeURI(fileName); // 下载后文件名
 
      document.body.appendChild(a);
 
      a.click(); // 点击下载
 
      document.body.removeChild(a); // 下载完成移除元素
 
      window.URL.revokeObjectURL(href); // 释放掉blob对象

这种方式主要将文件流转换成Blob对象,并利用URL.createObjectURL生成url地址,然后再利用a标签下载。

但这种同样存在兼容性问题,IE10以下不可用。

三、对于base64文件进行下载

downFile(fileBase64) {
      // fileBase64是获取到的图片base64编码
      const imgUrl = `data:image/png;base64,${fileBase64}`
      if (window.navigator.msSaveOrOpenBlob) {
        const bstr = atob(imgUrl.split(',')[1])
        let n = bstr.length
        const u8arr = new Uint8Array(n)
        while (n--) {
          u8arr[n] = bstr.charCodeAt(n)
        }
        const blob = new Blob([u8arr])
        window.navigator.msSaveOrOpenBlob(blob, fileName + '.' + 'png')
      } else {
        const a = document.createElement('a')
        a.href = imgUrl
        a.setAttribute('download', fileName)
        a.click()
      }
}

IE10以下的兼容性问题依然存在。

以上文件下载方式,或多或少都存在一些限制或兼容性问题,有没有完美的解决方案,欢迎大佬们指教!