前端 javascript 实现文件下载的示例


Posted in Javascript onNovember 24, 2020

在 html5 中,a 标签新增了 download 属性,包含该属性的链接被点击时,浏览器会以下载文件方式下载 href 属性上的链接。示例:

<a href="https://www.baidu.com" rel="external nofollow" download="baidu.html">下载</a>

1. 前端 js 下载实现与示例

通过 javascript 动态创建一个包含 download 属性的 a 元素,再触发点击事件,即可实现前端下载。

代码示例:

function download(href, title) {
    const a = document.createElement('a');
    a.setAttribute('href', href);
    a.setAttribute('download', title);
    a.click();
}

说明:

  • href 属性设置要下载的文件地址。这个地址支持多种方式的格式,因此可以实现丰富的下载方法。
  • download 属性设置了下载文件的名称。但 href 属性为普通链接并且跨域时,该属性值设置多数情况下会被浏览器忽略。

1.1 普通连接下载示例

// 下载图片
download('https://lzw.me/images/gravatar.gif', 'lzwme-gravatar');
// 下载一个连接
download('https://lzw.me', 'lzwme-index.html');

1.2 href 为 data URIs 示例
data URI 是前缀为 data:scheme 的 URL,允许内容创建者在文档中嵌入小文件。数据URI由四个部分组成:前缀(数据:),指示数据类型的MIME类型,如果非文本则为可选的base64令牌,数据本身:

data:[<mediatype>][;base64],<data>

链接的 href 属性为 data URIs 时,也可以实现文件内容的下载。示例:

download('data:,Hello%2C%20World!', 'data-uris.txt');
download('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D', 'data-uris.txt');

1.3 canvas 下载示例
对于 canvas 可以通过 toDataURL 方法取得 data URIs 格式的内容。

1.4 二进制内容下载
URL.createObjectURL 方法会根据传入的参数创建一个指向该参数对象的 URL。新的对象 URL 指向执行的 File 对象或者是 Blob 对象。

URL.createObjectURL 的参数是 File 对象或者 Blob 对象,File 对象也就是通过 input[type=file] 选择的文件,Blob 对象是二进制数据。

将URL.createObjectURL 返回值设为 href 属性的值,即可实现二进制内容下载。示例:

const content = 'Welcome to lzw.me!';
const blob = new Blob([content]);
const href = URL.createObjectURL(blob);
download(href, 'download-text.txt');
URL.revokeObjectURL(href);

1.5 前端下载方法示例
综合上述讨论,这里给出一个前端实现下载的 saveAs 方法的 TypeScript 示例:

/**
 * 通过创建 a dom 对象方式实现前端文件下载
 * @param href 要下载的内容链接。当定义了 toBlob 时,可以为纯文本或二进制数据(取决于 toBlob 格式
 * @param fileName 下载后的文件名称
 * @param toBlob 如设置该参数,则通过 blob 方式将 href 转换为要保存的文件内容,该参数将入参为 new Blob([href], toBlob) 的第二个参数
 * @example
 * ```js
 * saveAs('abc', 'abc.txt', {});
 * saveAs('data:,Hello%2C%20World!', 'hello.txt');
 * saveAs('https://lzw.me/images/avatar/lzwme-80x80.png', 'lzwme-logo.png');
 * ```
 */
export function saveAs(href: string | Blob, fileName?: string, toBlob?: PlainObject) {
 const isBlob = href instanceof Blob || toBlob;
 if (!fileName && typeof href === 'string' && href.startsWith('http')) {
  fileName = href.slice(href.lastIndexOf('/') + 1);
 }
 fileName = decodeURIComponent(fileName || 'download');
 if (typeof href === 'string' && toBlob) href = new Blob([href], toBlob);
 if (href instanceof Blob) href = URL.createObjectURL(href);
 const aLink = document.createElement('a');
 aLink.setAttribute('href', href);
 aLink.setAttribute('download', fileName);
 aLink.click();
 // const evt = document.createEvent("HTMLEvents");
 // evt.initEvent("click", false, false);
 // aLink.dispatchEvent(evt);
 if (isBlob) setTimeout(() => URL.revokeObjectURL(aLink.href), 100);
 return aLink;
}

2.检测浏览器是否支持 download 属性

download 属性为 html5 新增内容,浏览器支持情况可参考:http://caniuse.com/#feat=download

<img src="https://lzw.me/wp-content/uploads/2017/04/a-download.png" alt="" width="879" height="346" class="aligncenter size-full wp-image-2330" />

判断浏览器是否支持该属性,只需要检测 a 标签是否存在 download 属性。示例:

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

对于不支持的浏览器,只能另想他法或者予以降级处理了。

3.使用 serviceWorker 和 fetch API 代理实现

前端下载更多的需求是因为内容产生于前端。那么可以在后端实现一个这样的 API ,它在接收到前端发出的内容后返回下载格式的数据。这种实现就不存在浏览器兼容问题。

利用 serviceWorker 和 fetch API 截拦浏览器请求,只需实现好约定逻辑,也可实现这种功能需求。示例:

在页面中,通过 fetch API 构造请求:

fetch('lzwme.txt', {
    isDownload: true,
    body: {
        data: new Blob('hi!')
    }
})

在 serviceWorker 中,截拦附带 isDownload 头信息的请求,构造下载回应:

self.addEventListener('fetch', function(event) {
    const req = event.request;
    if (!req.headers.get('isDownload')) {
        retrun fetch(req);
    }
    const filename = encodeURIComponent(req.url);
    const contentType = req.headers.get('Content-Type') || 'application/force-download';
    const disposition = "inline;filename=" + filename + ";filename*=utf-8''" + filename
    const myBody = req.headers.get(body).data;
    event.respondWith(
        new Response(myBody, {
            headers: {
                'Content-Type': contentType,
                'Content-Disposition': disposition
            }
        })
    );
});

4 使用 ajax (xhr与fetch API) 方式下载服务器文件

以上主要讨论的是纯前端实现下载保存文件的方法。对于下载服务器文件,最简的方式就是 window.open(url) 和 location.href=url 了,但是其的弊端也很明显,当出错时整个页面都会挂掉,而且也无法获得下载状态与进度,下载时间稍长时体验相当不好。

下面介绍一下使用 xhr 和 fetch API 实现文件下载的方法。其主要思路为:将请求结果设为 Blob 类型,然后采用前端下载保存 Blob 类型数据的方式实现下载。

4.1 使用 xhr 下载远程服务器文件
代码示例:

/** 前端下载/保存文件 */
function saveAs(href, fileName) {
  const isBlob = href instanceof Blob;
  const aLink = document.createElement('a');
  aLink.href = isBlob ? window.URL.createObjectURL(href) : href;
  aLink.download = fileName;
  aLink.click();
  if (isBlob) setTimeout(() => URL.revokeObjectURL(aLink.href), 100);
}
function xhrDownload(url, options = {}) {
  options = Object.assign({ method: 'get', headers: {} }, options);
  return new Promise((reslove, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.responseType = 'blob'; // options.responseType;
    if (options.headers) {
      for (const key in options.headers) xhr.setRequestHeader(key, options.headers[key]);
    }
    xhr.onload = () => {
      // 从 Content-Disposition 中获取文件名示例
      const cd = xhr.getResponseHeader('Content-Disposition');
      if (cd && cd.includes('fileName') && !options.fileName) options.fileName = cd.split('fileName=')[1];
      options.fileName = decodeURIComponent(options.fileName || 'download-file');
      if (+xhr.status == 200) {
        saveAs(xhr.response, options.fileName);
        reslove(options.fileName);

使用 fecth API 下载远程服务器文件

function fetchDownload(url, options = {}) {
  options = Object.assign({ credentials: 'include', method: 'get', headers: {} }, options);
  return fetch(url, options).then(response => {
    return response.blob().then(blob => {
      if (!blob || !blob.size) return Promise.reject('empty');
      // 从 Content-Disposition 中获取文件名示例
      const cd = response.headers.get('Content-Disposition');
      if (cd && cd.includes('fileName') && !options.fileName) options.fileName = cd.split('fileName=')[1];
      options.fileName = decodeURIComponent(options.fileName || 'download-file');
      saveAs(blob, options.fileName);
      return options.fileName;
    });
  });
}
// 测试
fetchDownload('https://lzw.me/images/avatar/lzwme-80x80.png', {
    // method: 'post',
    // headers: {
    //   'Content-Type': 'application/json'
    // },
    // body: JSON.stringify({
    //   pageSize: 100000,
    //   startPage: 0
    // })
  })

以上就是前端 javascript 实现文件下载的示例的详细内容,更多关于JavaScript 文件下载的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
javascript while语句和do while语句的区别分析
Dec 08 Javascript
js字符编码函数区别分析
Dec 28 Javascript
再谈JavaScript异步编程
Jan 27 Javascript
AngularJS 使用 UI Router 实现表单向导
Jan 29 Javascript
javascript+css3 实现动态按钮菜单特效
Feb 06 Javascript
JavaScript判断变量是否为数组的方法(Array)
Feb 24 Javascript
jQuery each函数源码分析
May 25 Javascript
玩转JavaScript OOP - 类的实现详解
Jun 08 Javascript
JS实现太极旋转思路分析
Dec 09 Javascript
浅谈Node异步编程的机制
Oct 18 Javascript
详解React之key的使用和实践
Sep 29 Javascript
ant-design-vue 实现表格内部字段验证功能
Dec 16 Javascript
如何使用 JavaScript 操作浏览器历史记录 API
Nov 24 #Javascript
JavaScript实现鼠标移入随机变换颜色
Nov 24 #Javascript
原生js实现表格循环滚动
Nov 24 #Javascript
浅析VUE防抖与节流
Nov 24 #Vue.js
解决vue页面刷新,数据丢失的问题
Nov 24 #Vue.js
Js利用正则表达式去除字符串的中括号
Nov 23 #Javascript
jQuery实现动态操作table行
Nov 23 #jQuery
You might like
人工智能开始玩《星际争霸2》 你的操作跟得上吗?
2017/08/11 星际争霸
一个更简单的无限级分类菜单代码
2007/01/16 PHP
基于PHP实现通过照片获取ip地址
2016/04/26 PHP
Yii2框架RESTful API 格式化响应,授权认证和速率限制三部分详解
2016/11/10 PHP
Jquery选择器 $实现原理
2009/12/02 Javascript
JavaScript 学习初步 入门教程
2010/03/25 Javascript
解决JS浮点数运算出现Bug的方法
2013/03/12 Javascript
DIV+CSS+JS不间断横向滚动实现代码
2013/03/19 Javascript
让复选框只能选择一项的方法
2013/10/08 Javascript
jQuery实现的仿select功能代码
2015/08/19 Javascript
jQuery解析返回的xml和json方法详解
2017/01/05 Javascript
js中怎么判断两个字符串相等的实例
2019/01/17 Javascript
使用vue2.6实现抖音【时间轮盘】屏保效果附源码
2019/04/24 Javascript
js计算两个时间差 天 时 分 秒 毫秒的代码
2019/05/21 Javascript
react实现antd线上主题动态切换功能
2019/08/12 Javascript
JavaScript实现轮播图效果代码实例
2019/09/28 Javascript
javascript实现计算器功能
2020/03/30 Javascript
javascript代码实现简易计算器
2021/01/25 Javascript
[58:29]DOTA2-DPC中国联赛 正赛 Phoenix vs XG BO3 第一场 1月31日
2021/03/11 DOTA
python通过文件头判断文件类型
2015/10/30 Python
详解python调度框架APScheduler使用
2017/03/28 Python
解决python文件字符串转列表时遇到空行的问题
2017/07/09 Python
django 使用 request 获取浏览器发送的参数示例代码
2018/06/11 Python
Python分割指定页数的pdf文件方法
2018/10/26 Python
解决Python3 抓取微信账单信息问题
2019/07/19 Python
Django对models里的objects的使用详解
2019/08/17 Python
Django实现将views.py中的数据传递到前端html页面,并展示
2020/03/16 Python
Python 实现一行输入多个数字(用空格隔开)
2020/04/29 Python
6号汽车旅馆预订:Motel 6
2018/02/11 全球购物
《放小鸟》教学反思
2014/04/20 职场文书
小学作文评语大全
2014/04/21 职场文书
门店业绩提升方案
2014/06/08 职场文书
一个独生女的故事观后感
2015/06/04 职场文书
如何用python插入独创性声明
2021/03/31 Python
MongoDB修改oplog大小的四种方法
2022/04/11 MongoDB
解决flex布局中子项目尺寸不受flex-shrink限制
2022/05/11 HTML / CSS