当前位置: 首页 > news >正文

StreamSaver.js入门教程:优雅解决前端下载文件的难题

本文简介

点赞 + 关注 + 收藏 = 学会了


本文介绍一个能让前端优雅下载大文件的工具:StreamSaver.js

  • ⚡️ StreamSaver.js GitHub地址
  • ⚡️ 官方案例

StreamSaver.js 可用于实现在Web浏览器中直接将大文件流式传输到用户设备的功能。

传统的下载方式可能导致大文件的加载时间较长或造成内存占用过大的问题,使用 <a> 标签打开新页面下载文件,遇到 .txt 或者 .mp4 之类的文件可能就直接在页面展示了,不会触发下载功能。而 StreamSaver.js 则通过流式下载的方式解决了这些问题。

StreamSaver.js 将大文件拆分成小块,并在下载过程中逐块传输到硬盘,从而降低内存占用和提高下载速度。



环境准备

要学习 StreamSaver.js 首先要准备一份或者多份可下载的文件。

你可以使用网络上的文件资源,但这需要你自己去找。

你也可以在自己的电脑运行个服务,把文件资源丢进去即可。


如果你用脚手架创建项目,比如vue或者react之类的项目,也可以把文件放在静态资源目录里。

比如用 vite 创建一个 Vue 项目,然后在 public 目录下创建一个 test.txt 文件。项目运行起来,在浏览器访问 http://localhost:端口号/public/test.txt 就能查看到这个文件内容。



安装 StreamSaver.js

可以使用 CDN 或者 npm 安装 StreamSaver.js

本文使用 CDN 的方式讲解。


CDN

打开 StreamSaver.js的仓库。

file

StreamSaver.js 文件下载到你项目里引入即可。

<script src="../StreamSaver.js"></script>

npm

⚡️StreamSaver npm地址


使用以下命令下载 StreamSaver 到项目里

npm i streamsaver

然后在要使用的地方引入即可。

import streamSaver from "streamsaver"


起步

起步阶段,我们先试试如何下载一个 .txt 文件。

如果我们要下载一些浏览器读不懂的文件,我们可以使用 <a>标签在新窗口打开链接,也可以使用 windows.open('url') 的方式打开新窗口进行下载。

但如果这个文件浏览器是读得懂的,比如 .txt 文件,那浏览器就不会执行下载,而是会直接在页面中把文件内容展示出来。

此时就可以使用 StreamSaver.js 来解决这个问题。

使用 StreamSaver.js 下载文件的大概流程是这样的(为了方便理解,我用一些不专业的术语进行描述):

  1. 创建一个文件,该文件支持写入操作。streamSaver.createWriteStream('文件名.后缀')
  2. 使用 fetch 方法访问文件的url,将内容一点点的放到 StreamSaver 创建的文件里。
  3. 监听文件内容是否读取完整,读取完就执行“保存并关闭文件”的操作。

根据上面的指引编写代码:

<!-- 下载按钮 -->
<button id="download">下载</button><!-- 引入StreamSaver.js -->
<script src="../StreamSaver.js"></script>
<script>// 监听按钮点击事件,点击就下载文件download.onclick = () => {// 【步骤1】创建一个文件,该文件支持写入操作const fileStream = streamSaver.createWriteStream('test.txt') // 这里传入的是下载后的文件名,这个名字可以自定义// 【步骤2】使用 fetch 方法访问文件的url,将内容一点点的放到 StreamSaver 创建的文件里fetch('http://localhost:9988/public/test.txt').then(res => {const readableStream = res.bodyif (window.WritableStream && readableStream.pipeTo) {return readableStream.pipeTo(fileStream).then(() => console.log('完成写入'))}// 【步骤3】监听文件内容是否读取完整,读取完就执行“保存并关闭文件”的操作。window.writer = fileStream.getWriter()const reader = res.body.getReader()const pump = () => reader.read().then(res => res.done? writer.close(): writer.write(res.value).then(pump))pump()})}
</script>

大概就是这样子了。


🔔提示

如果遇到提示跨域的问题,可以配置 mitm 指向 mitm.html

mitm.htmlStreamSaver.js仓库 里可以获取到。

file

可以把 mitm.html 放到你服务器再配置。

streamSaver.mitm = 'https://你的服务器地址/mitm.html'


打包下载 zip

如果想将多个文件打包成zip下载到本地,可以将 StreamSaver.jszip-stream.js 结合在一起使用。

zip-stream.jsStreamSaver.js 的仓库里有。

file

zip-stream.js/examples 目录里。


使用 npm 安装 streamsaver 也能在 /examples 目录下找到 zip-stream.js,直接引入即可。

import 'node_modules/streamsaver/examples/zip-stream.js'

如果使用 CDN 的方式就直接用 <script src='zip-stream.js'></script> 引入即可。


打包zip下载的步骤:

  1. 创建下载后的文件名和文件格式。
  2. 使用 zip-stream 创建一个 ZIP 实例,用来不断接收要下载的文件。
  3. 所有文件下载完成就执行 close() 方法将所有文件真正打包成一个 zip

<button id="download">下载</button><script src="../StreamSaver.js"></script>
<script src="zip-stream.js"></script>
<script>// 要下载的文件地址列表let urls = [{fileName: 'test.txt',url: 'http://localhost:9988/public/test.txt',},{fileName: 'test.csv',url: 'http://localhost:9988/public/test.csv',}]download.onclick = () => {// 【步骤1】const fileStream = streamSaver.createWriteStream('test.zip')// 【步骤2】const readableZipStream = new ZIP({async pull(ctrl) {for (let i = 0; i < urls.length; i++) {const res = await fetch(urls[i].url)const stream = () => res.bodyconst name = urls[i].fileNamectrl.enqueue({ name, stream }) // 不断接收要下载的文件}// 【步骤3】ctrl.close()}})if (window.WritableStream && readableZipStream.pipeTo) {return readableZipStream.pipeTo(fileStream).then(() => console.log('下载完了'))}}</script>

点击下载按钮后的效果:

file

这个例子准备了 .csv.txt 文件。下载时会合并成 .zip,解压后能看到里面的所有文件都是正常能打开的 。



合成文件再下载

在这个例子中,我要将2个 .csv 文件合并成1个再下载。

我准备了两个 .csv 文件(test1.csv 和 test2.csv),它们的内容分别长这个样子。

file

我要将它们合并成这样子:

file


在合并文件之前我们首先要清楚这个文件的内容是如何组成的。

在 Excel 中打开 .csv 的每个单元格的内容转换成文本形式的话是用逗号分隔。

file

如果要合并多个 .csv 文件,只需监听到每个 .csv 下载完成,然后再拼接一个 \n 换行,再下载下一个 .csv 文件即可。


整理一下就是以下几个步骤:

  1. 拿到一组下载地址,把它们转存到一个迭代器里。
  2. 递归执行迭代器,如果迭代器里还有内容,就使用 fetch 请求数据。
  3. 如果迭代器没内容了,使用 writer.close() 关闭文件写入。

该功能写成真正的代码如下所示:

<button id="download" οnclick="down()">下载</button><script src="../StreamSaver.js"></script>
<script>// 编码转换方法let encode = TextEncoder.prototype.encode.bind(new TextEncoder)// 准备好要下载的链接const urls = ['http://localhost:9988/public/test1.csv','http://localhost:9988/public/test2.csv']// 迭代器数据let urlsIter = null// 写入方法放到全局中保存let writer = null// 下载按钮点击事件function down() {// 创建一个下载管道,并将下载后的文件命名为 newTest.csvconst fileStream = streamSaver.createWriteStream('newTest.csv')// 创建写入方法writer = fileStream.getWriter()// 将要下载的链接转换成迭代器urlsIter = urls[Symbol.iterator]()// 开始执行循环下载forDown()}// 循环下载的方法async function forDown() {// 获取迭代器最新一条数据let urlIter = urlsIter.next()// 如果迭代器没数据,执行写入完成操作,并停止递归if (urlIter.done) {writer.close()return}// 迭代器有内容时执行请求操作await fetch(urlIter.value).then(res => {// 通过请求文件url获取到的数据const readableStream = res.bodyif (window.WritableStream && readableStream.pipeTo) {const reader = readableStream.getReader()// 讲获取到的每一包写入文件里const pump = () => {return reader.read().then(readRes => {if (readRes.done) { // 当前文件读取完成后执行// 文件读取完成后换行writer.write(encode('\n'))// 执行请求下一个文件forDown()} else { // 文件读取过程执行// 一包包写入writer.write(readRes.value).then(pump)}})}pump()}})}
</script>

这个案例稍微复杂一丢丢,建议跟着手敲一遍。



推荐阅读

除了本文所讲内容外,StreamSaver.js 的仓库还有好几个实用的例子,有需要的工友可以去看看。


👍《前端中文汉字转拼音》

👍《提升日期处理效率:day.js 实战经验分享》

👍《眨个眼就学会了Pixi.js》

👍《OpenLayers.js 入门教程:打造互动地图的入门指南》

👍《物理世界的互动之旅:Matter.js入门指南》

👍《p5.js 光速入门》


点赞 + 关注 + 收藏 = 学会了 代码仓库

相关文章:

StreamSaver.js入门教程:优雅解决前端下载文件的难题

本文简介 点赞 关注 收藏 学会了 本文介绍一个能让前端优雅下载大文件的工具&#xff1a;StreamSaver.js ⚡️ StreamSaver.js GitHub地址⚡️ 官方案例 StreamSaver.js 可用于实现在Web浏览器中直接将大文件流式传输到用户设备的功能。 传统的下载方式可能导致大文件的加…...

element-ui vue2 iframe 嵌入外链新解

效果如图 实现原理 在路由中通过 props 传值 {path: /iframe,component: Layout,meta: { title: 小助手, icon: example },children: [{path: chatglm,name: chatglm,props: { name: chatglm,url: https://chatglm.cn },component: () > import(/views/iframe/common),me…...

win10 + VS2017 编译libjpeg(jpeg-9b)

需要用到的文件&#xff1a; jpeg-9b.zip win32.mak 下载链接链接&#xff1a;https://pan.baidu.com/s/1Z0fwbi74-ZSMjSej-0dV2A 提取码&#xff1a;huhu 步骤1&#xff1a;下载并解压jpeg-9b。 这里把jpeg-9b解压到文件夹"D:\build-libs\jpeg\build\jpeg-9b" …...

如何实现公网远程桌面访问Ubuntu?VNC+cpolar内网穿透!

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…...

SpringMvc接收参数

接受参数:1.路径设置RequestMapping(value"地址",method"请求方式") 类|方法GetMapping PostMapping 方法2.接受参数[重点]param直接接收---handler(类型 形参名) 形参名请求参数名注解指定---handler(RequestParam(name"请求参…...

计算机网络文章荟萃

脑残式网络编程入门(二)&#xff1a;我们在读写Socket时&#xff0c;究竟在读写什么&#xff1f;-网络编程/专项技术区 - 即时通讯开发者社区! 1.什么是 socket - 掘金2.socket 的实现原理 - 掘金本文讲述了 socket 在 linux 操作系统下的数据结构&#xff0c;以及阻塞 IO 利用…...

C# Socket通信从入门到精通(4)——多个异步TCP客户端C#代码实现

前言: 在之前的文章C# Socket通信从入门到精通(3)——单个异步TCP客户端C#代码实现我介绍了单个异步Tcp客户端的c#代码实现,但是有的时候,我们需要连接多个服务器,并且对于每个服务器,我们都有一些比如异步连接、异步发送、异步接收的操作,那么这时候我们使用之前单个…...

GitHub为自己的仓库(Repository)设置默认代码缩进(tabsize)

无意中发现GitHub默认显示tab为8个空格的大小&#xff0c;十分不适&#xff0c;故想改成四个字节的缩进 流程 GitHub是支持EditorConfig的。所有只需在Repository根目录下(注意不是.git文件夹下)新建文件 .editorconfig vim .editorconfig内容如下 # top-most EditorConfig…...

Tomcat的动静分离

一、动态负载均衡 3、台虚拟机模拟&#xff1a; 代理服务器&#xff1a;51 tomcat动态页面&#xff1a;53,54 关闭防火墙和安全机制 配置代理服务器&#xff0c;由于做的是七层代理&#xff0c;所以要在http模块配置 配置前端页面 <!DOCTYPE html> <html> <…...

Chimera:混合的 RLWE-FHE 方案

参考文献&#xff1a; [HS14] S. Halevi and V. Shoup. Algorithms in HElib. In Advances in Cryptology–CRYPTO 2014, pages 554–571. Springer, 2014.[HS15] S. Halevi and V. Shoup. Bootstrapping for HElib. In Advances in Cryptology–EUROCRYPT 2015, pages 641–6…...

MySQL 连接出现 Authentication plugin ‘caching_sha2_password的处理方法(使用第二种)

出现这个原因是mysql8 之前的版本中加密规则是mysql_native_password,而在mysql8之后,加密规则是caching_sha2_password, 解决问题方法有两种,一种是升级navicat驱动,一种是把mysql用户登录密码加密规则还原成mysql_native_password. 1. 升级MySQL版本 较早的MySQL版本可能不…...

设置Ubuntu 20.04的静态IP地址(wifi模式下)

一、引言 自己家用的Ubuntu的&#xff0c;重启后ip地址经常会改变&#xff0c;这个时候就需要我们手动配置静态IP了。 二、优点 给Ubuntu设置一个静态IP地址有以下几个好处&#xff1a; 持久性&#xff1a;静态IP地址是固定不变的&#xff0c;与设备的MAC地址绑定。这意味着…...

Qt界面实现中英文切换

要实现的效果&#xff0c;是下拉列表切换中文和English实现按钮文本中英文内容切换。 实现步骤&#xff1a; 1.在VS中鼠标对Translation Files文件右击&#xff0c;选择“添加”--->“模块”. 在弹窗的窗口中选择“Qt”--->“Qt Translation File”。 添加Translation_e…...

Python 编写确定个位、十位以上方法及各数位的和程序

Python 编写确定数字位方法 Python 编写确定个位、十位Python 编写确定个位、十位、百位方法解析&#xff1a;Python 各数位的和程序 利用%&#xff08;取余符号&#xff09;、//&#xff08;整除&#xff09;符号。 Python 编写确定个位、十位 num 17 a num % 10 b num /…...

AI 引擎系列 1 - 从 AI 引擎工具开始(2022.1 更新)

AI 引擎系列 1 - 从 AI 引擎工具开始&#xff08;2022.1 更新&#xff09; AI 引擎系列简介 在这篇题为 Versal 自适应 SoC AI 引擎入门的文章中&#xff0c;我介绍了一些 Versal™ 自适应 SoC 器件中存在的 AI 引擎 (AIE) 阵列。本系列是全新的 AI 引擎系列博文&#xff0c;我…...

Android Kotlin 协程初探 | 京东物流技术团队

1 它是什么&#xff08;协程 和 Kotlin协程&#xff09; 1.1 协程是什么 维基百科&#xff1a;协程&#xff0c;英文Coroutine [kəru’tin] &#xff08;可入厅&#xff09;&#xff0c;是计算机程序的一类组件&#xff0c;推广了协作式多任务的子程序&#xff0c;允许执行被…...

MySQL-DQL【数据查询语言】(图码结合)

作者:chlorine 专栏:数据库_chlorine5的博客-CSDN博客 MySQL——DDL&#xff1a;DDL ——数据定义语言 MySQL——DML&#xff1a;DML——数据操作语言 目录 一.DQL的定义 二.DQL—语法 三.DQL—基础查询(SELECT.. FROM) &#x1f449;查询多个字段 &#x1f449;设置别名 …...

HTTP 响应状态码介绍

HTTP 响应状态码用来表明特定 HTTP 请求是否成功完成。响应被归为以下五大类&#xff1a; 1xx 信息响应 (100–199) 2xx 成功响应 (200–299) 3xx 重定向消息 (300–399) 4xx 客户端错误响应 (400–499) 5xx 服务端错误响应 (500–599)1xx 信息响应 100 Continue 这个临时响应…...

vtk 绘制等高线

使用 VTK 也可以实现等高线绘制&#xff0c;在地图、气象、地质等领域中&#xff0c;经常需要将数据以多个等高线的形式进行展示。在VTK(Visualization Tookt) 中&#xff0c;有专门的AP何以支持简便地实现这一功能。 我们还可以根据算法操作的数据类型或生成的数据类型对算法…...

N-129基于springboot,vue学生宿舍管理系统

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vuevue-element-admin 服务端技术&#xff1a;springboot,mybatis…...

力扣每日一题70:爬楼梯

题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶 1 阶 …...

分布式合集

1.Spring 的事务 Spring框架为应用程序提供了强大的事务管理功能。它通过将事务逻辑与业务逻辑分离&#xff0c;使得开发者可以专注于业务逻辑的实现&#xff0c;而不必过多关注事务的管理。Spring事务的核心是基于AOP&#xff08;面向切面编程&#xff09;的声明式事务管理&a…...

MySQL比较两个表数据的差异

一、几种比较方式 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;连接两个表的相同记录&#xff0c;通过比较连接后的结果集&#xff0c;找出相同和不同的数据。外连接&#xff08;LEFT JOIN或RIGHT JOIN&#xff09;&#xff1a;连接两个表的所有记录&#xff0c;包括…...

hive使用中的参数优化与问题排查

1.使用hive的虚拟列排查错误案例 set hive.exec.rowoffsettrue; SELECT –输入文件名 INPUT__FILE__NAME, –文件中的块内偏移量 BLOCK__OFFSET__INSIDE__FILE, –文件行偏移量 ROW__OFFSET__INSIDE__BLOCK, * from hdp_lbg_zhaopin_defaultdb.zzdetail where dt‘20201117’…...

Leetcode—66.加一【简单】

2023每日刷题&#xff08;十一&#xff09; Leetcode—66.加一 实现代码1 /*** Note: The returned array must be malloced, assume caller calls free().*/ int* plusOne(int* digits, int digitsSize, int* returnSize){int num 0;int i 0;int arr[110] {0};// 进位标识…...

Babylonjs学习笔记(六)——贴图的使用

书接上回&#xff0c;这里讨论贴图的运用&#xff01;&#xff01;&#xff01; // 创建球网格const ball MeshBuilder.CreateSphere(ball,{diameter:1},scene)ball.position new Vector3(0,1,0)// 创建PRB材质const ballMat new PBRMaterial(pbr,scene)// albedoTexture 反…...

架构风格区别-架构案例(五十九)

管道-过滤器和仓库的区别&#xff1f; 独立的数据仓库&#xff0c;处理流独立&#xff0c;处理数据用连接仓库工具数据与处理在一起&#xff0c;改动的话需要重启系统需要仓库工具与仓库连接&#xff0c;数据与处理分离&#xff0c;性能差可以支持并发连接访问仓库&#xff0c…...

p5.js画布操作实战:创建,绑定指定元素,动态调整大小,隐藏滚动条,删除画布

文章简介 之前在 《p5.js 光速入门》 里粗略讲过一下如何使用 p5.js 创建画布。 这次要介绍几个 p5.js 提供的画布相关的方法。 创建画布时的相关配置。让画布绑定指定元素。重置画布大小。删除画布。 学习本文前你需要具备一点 p5.js 的知识&#xff0c;想了解的请查看 《p…...

vue手动拖入和导入excel模版

1.列表按钮 <el-button click“importExcel(scope.row.id)” size“small” type“text”>导入excel模版 2.按钮弹框 3.data定义数据 data () { return { projectId: ‘’, importDialogVisible: false, fileList: [], //手动上传 upload_file: ‘’, //导入excel模版…...

Linux下导出dump文件(Oracle和PG数据)

dump文件可以快速的导入导出&#xff0c;所以在数据量较大的情况下用其他方法导出数据都不如dump。 不管是什么数据库&#xff0c;第一步都需要登录Oracle用户 su - oracle登录之后可以选择导出文件到当前目录 Oracle数据库导出指定表&#xff1a; exp 数据库用户名/密码lo…...