前端文件下载方法总结

前端文件下载方法总结

1、open或location.href

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

前提:下载地址不需要鉴权 以及 不存在跨域问题

javascript 复制代码
window.location.href = url // url为下载地址
window.open('downloadFile.zip');

优点:简单方便

缺点:

  • 直接访问可能会覆盖当前页面地址
  • 不能添加header,也就不能进行鉴权
  • 会出现URL长度限制问题
  • 需要注意url编码问题
  • 浏览器可直接浏览的文件类型是不提供下载的,如txt、png、jpg、gif、pdf等
  • 无法知道下载的进度

2、用a标签下载

a标签可以访问下载文件的地址。如果 是对于浏览器可以直接浏览的文件类型:jpg\png\gif\txt等,a标签不能直接下载。

所以,在html5中,a标签提供了 download属性。

简单用法:

javascript 复制代码
<a href="example.jpg" download>点击下载</a>

//如果带属性值 指定下载的文件名,即重命名下载文件。不设置的话默认是文件原本名。
<a href="example.jpg" download="test">点击下载</a>

动态a标签:

下面的url即文件或接口的地址

如需要额外参数,通过url后问号拼接参数,后端get请求方式接收

服务端需要配置url资源的响应头Content-disposition值为attachment,浏览器识别会调用下载弹窗,进一步配置filename值即下载的默认文件名。

javascript 复制代码
// 封装
function downloadFile (url, name) {
  url = url || ''
  name = name || ''
  let ele = document.createElement('a')
  ele.target = '_blank'
  ele.href = url
  ele.download = name
  ele.click()
  ele = null
}

// 调用
downloadFile(url)

判断浏览器是否支持download属性:

javascript 复制代码
const isSupport = 'download' in document.createElement('a');

需要注意一些信息:

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

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

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

优点:download属性能解决不能直接下载浏览器可浏览的文件

缺点:

  • 不能下载跨域下的浏览器可浏览的文件
  • 不能进行鉴权
  • url资源或接口响应头配置了filename时,前端无法自定义下载文件名。
  • 有兼容性问题,特别是IE
  • 前端需要自定义展示下载进度条需求时无法支持。

3、利用Blob下载,发送ajax请求api获取文件流进行下载

这种方法除了能利用已知文件地址路径进行下载外,还适用于需要调取API获取文件流下载的形式。

有些时候后端不会直接提供一个下载地址给你直接访问,而是要调api。

方法:

前端请求接口设置responseType: 'blob', 统一 转为Blob 数据,然后利用URL.createObjectUrl生成url地址,赋值在a标签的href属性上,结合download进行下载.

blob 是 js 里表示二进制文件的对象。
target.response就是一个Blob对象,打印出来会看到两个属性size和type。

2种方法代码参考

文件下载方法参考,第二种可设置下载进度。

参考:https://blog.csdn.net/u010059669/article/details/122623034

javascript 复制代码
/**
 * 第一种方法:
 * @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);
        }
    };
}

/**
 * 第二种方法:
 * @description: ajax下载/导出文件
 * @param {string} url 资源或接口地址,可携带参数
 * @param {function} progressCallback 选填,前端自定义的下载进度回调
 */
function downloadFromApi (url, progressCallback) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.responseType = 'blob'
    xhr.onload = e => {
      if (xhr.status === 200) {
        const response = xhr.response
        resolve(response)

        const dispoition = xhr.getResponseHeader('Content-Disposition') || ''
        const nameStr = dispoition.split(';')[1] || ''
        const fileName = decodeURIComponent(nameStr.split('=')[1] || '')

        if (window.navigator.msSaveOrOpenBlob) {
          // 兼容处理,ie下的blob下载
          window.navigator.msSaveOrOpenBlob(response, fileName)
        } else {
          const href = URL.createObjectURL(response)
		  let ele = document.createElement('a')
		  ele.target = '_blank'
		  ele.href = href
		  ele.download = fileName
		  ele.click()
		  ele = null
		  URL.revokeObjectURL(href)
        }
      } else {
        reject(new Error(`${xhr.status}:请求失败`))
      }
    }
    xhr.error = err => {
      reject(err)
    }
    xhr.onprogress = e => {
      if (e.lengthComputable) {
        const percentComplete = e.loaded / e.total
        progressCallback && progressCallback(percentComplete)
      }
    }
    xhr.send(null)
  })
}

下载进度设置的原理

服务端需要配置响应头的Content-Length,值为文件大小(未配置的话会在文件传输完成后才弹窗提示下载,响应感知太慢)。

前端监听xhr对象的progress事件:

javascript 复制代码
xhr.onprogress = e => {
  if (e.lengthComputable) {
    const percentComplete = e.loaded / e.total
    // 打印当前已完成的进度比例
    console.log(percentComplete)
  }
}

另一种封装方式(axios 请求)

如果是封装了axios请求,可以在调请求后,使用下面的封装的downLoadFile 方法,使用示例:

js 复制代码
/*
httpRequest: 封装的下载API请求,记得设置responseType = 'blob'
url: 接口
*/
 httpRequest(url).then(async (res) => {
   console.log('下载请求返回:', res);
   const { data, response } = res;
   if (data && response.status === 200) {
   	// 调用封装好的下载文件的方法
     downLoadFile(data, response);
   } 
 }).catch(async (err) => {
   console.log(err);
 });
 
/**
 * 下载文件
 * @param {any} data - 下载请求返回blob对象的data, 打印出来会有一个size和type 属性。
 * @param {Response} response- 请求的response
 */
const downLoadFile = (data: any, response: Response) => {
  let contentType = response.headers.get('content-type') || undefined;
  let contentDisposition = response.headers.get('content-disposition') || undefined;

  contentType = isExistValue(contentType) ? contentType : undefined;
  contentDisposition = isExistValue(contentDisposition) ? contentDisposition : undefined;

	// 文件名提取
  let filename = 'xxx';
  if (typeof contentDisposition === 'string') {
    contentDisposition = valueToLowerCase(contentDisposition);
    try {
      // 文件名提取
      filename = contentDisposition.match(/filename=(.*)/)[1];
    } catch (err) {
      console.log(err);
      // @ts-ignore
      filename = contentDisposition;
    }
  }
  // 创建一个 Blob 对象
  const blob = new Blob([data], { type: contentType });
  // @ts-ignore
  if (typeof window.navigator.msSaveBlob !== 'undefined') {
    // 兼容IE,window.navigator.msSaveBlob:以本地方式保存文件
    window.navigator.msSaveBlob(blob, decodeURI(filename));
  } else {
    const href = window.URL.createObjectURL(blob);
    const eLink = document.createElement('a'); // 创建一个<a>标签
    eLink.style.display = 'none'; // 隐藏标签
    eLink.href = href; // 配置href,指向本地文件的内存地址
    eLink.download = decodeURI(filename);
    document.body.appendChild(eLink);
    eLink.click();
    // 释放URL 对象
    document.body.removeChild(eLink);
    // 释放掉blob对象
    URL.revokeObjectURL(href);
  }
};

图片预览

如果是图片预览,也是后端返回的地址,需要带上token去请求。需要用到base64。伪代码参考思路:

javascript 复制代码
const [imgSrc, setImgSrc] = useState('');// 图片预览的src

useEffect(() => {
 const url = 'xxx.xx.xx/img/wx.png';
  // httpRequest自己封装的axios请求 , responseType 为 'blob'
  httpRequest(url).then(async (res) => {
    console.log('下载返回:', res);
    const { data, response } = res;
    if (data && response.status === 200) {
      // 生成图片预览的src
      const src = await blobToBase64(data);
      setImgSrc(src);
    }
  });
}, []);


/**
 * Blob 图像对象预览
 * */
const blobToBase64 = (file: Blob) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = (error) => reject(error);
  });
};

// 在Image标签中使用:
<Image width="80%" alt={name} src={imgSrc} />

优点:

  • 能解决不能直接下载浏览器可浏览的文件
  • 可添加鉴权信息

缺点: 兼容性问题,IE10以下不可用

相关推荐
无限聚核1 小时前
抖音 certificate.prepare 之后怎么验券?和美团 msconsume 差在哪,无限聚核
前端·微信小程序
2603_965896621 小时前
Grid 网格布局|CSS 二维页面排版体系
前端·css
有味道的男人1 小时前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript
lovingsoft2 小时前
Chrome Lighthouse 详解
前端·chrome
Qlly2 小时前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝2 小时前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦2 小时前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡3 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI3 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95273 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端