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

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...