当前位置: 首页 > 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…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

Unity中的transform.up

2025年6月8日&#xff0c;周日下午 在Unity中&#xff0c;transform.up是Transform组件的一个属性&#xff0c;表示游戏对象在世界空间中的“上”方向&#xff08;Y轴正方向&#xff09;&#xff0c;且会随对象旋转动态变化。以下是关键点解析&#xff1a; 基本定义 transfor…...

ubuntu22.04有线网络无法连接,图标也没了

今天突然无法有线网络无法连接任何设备&#xff0c;并且图标都没了 错误案例 往上一顿搜索&#xff0c;试了很多博客都不行&#xff0c;比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动&#xff0c;重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...

[特殊字符] 手撸 Redis 互斥锁那些坑

&#x1f4d6; 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作&#xff0c;想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁&#xff0c;也顺便跟 Redisson 的 RLock 机制对比了下&#xff0c;记录一波&#xff0c;别踩我踩过…...