粥里有勺糖

vuePress-theme-reco 粥里有勺糖    2018 - 2023
粥里有勺糖 粥里有勺糖

Choose mode

  • dark
  • auto
  • light
关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
author-avatar

粥里有勺糖

285

文章

40

标签

关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
  • source learn

    • 源码学习
    • 优秀装饰器源码学习(一):time
    • 优秀装饰器源码学习(二)
    • 优秀装饰器源码学习(三)
    • FileSaver.js源码学习,纯前端实现文件下载,防止浏览器直接打开预览
    • 源码学习:MongoDB-ObjectId生成原理
    • 源码学习:Vite中加载环境变量(loadEnv)的实现

FileSaver.js源码学习,纯前端实现文件下载,防止浏览器直接打开预览

vuePress-theme-reco 粥里有勺糖    2018 - 2023

FileSaver.js源码学习,纯前端实现文件下载,防止浏览器直接打开预览

粥里有勺糖 2021-06-07 技术笔记源码学习

# FileSaver.js源码学习,纯前端实现文件下载,防止浏览器直接打开预览

# 背景

在毕设项目 (opens new window)的开发中,有文件下载的场景

其中文件直接从OSS(对象存储服务)上下载文件,使用a标签直接指向下载资源的方案存在一些小问题

如果文件类型是浏览器支持的类型的话,将会直接打开预览,不会调用下载

如在下载图片,PDF等文件的时候如果直接打开预览,对用户来说非常不友好,体验较差

问题方案代码

function download(url){
  const a = document.createElement('a')
  a.target = '_blank'
  a.href = url
  document.body.append(a)
  a.click()
  a.remove()
}
1
2
3
4
5
6
7
8

首先想到的就是谷歌,然后就找到了这篇文章的主角FileSaver.js (opens new window)

# FileSaver.js简介

机翻

FileSaver.js 是在客户端保存文件的解决方案,非常适合在客户端生成文件的 Web 应用程序,但是如果文件来自服务器,我们建议您首先尝试使用 Content-Disposition 附件响应 标题,因为它具有更多的跨浏览器兼容性。

简述

纯前端下载文件的解决方案

# 使用

import { saveAs } from 'file-saver';
saveAs('sourceUrl')
// or
saveAs('sourceUrl','filename')
1
2
3
4

API非常简洁(吃鲸.jpg),非常好用

抱着敬畏与学习的态度,戳开了仓库的源码 (opens new window),好家伙只有100多行代码,这不得好好学习一番...

# 核心的下载方法

扫两遍源码,很快总结出了最小的,可运行代码

补齐了一些注释后如下

function download(url, name) {
  var xhr = new XMLHttpRequest()
  xhr.open('GET', url)
  // 设置返回数据的类型为blob
  xhr.responseType = 'blob'
  
  // 资源完成下载
  xhr.onload = function () {
    // 获取响应的blob对象
    const blob = xhr.response
    const a = document.createElement('a')
    // 设置下载的文件名字
    name = name || blob.name || 'download'
    a.download = name

    // 解决安全问题,新页面的window.opener 指向前一个页面的window对象
    // 使用noopener使 window.opener 获取的值为null
    a.rel = 'noopener'

    // 创建一个DOMString指向这个blob
    // 简单理解就是为这个blob对象生成一个可访问的链接
    a.href = URL.createObjectURL(blob)

    // 40s后移除这个临时链接
    setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s
    // 触发a标签,执行下载
    setTimeout(function () {
      a.dispatchEvent(new MouseEvent('click'))
    }, 0)
  }
  // 发送请求
  xhr.send()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

使用示例

<body>
    <button id="btn">下载</button>
    <script src="./index.js"></script>
</body>
1
2
3
4
document.getElementById('btn').addEventListener('click', function () {
  const url = 'https://img.cdn.sugarat.top/mdImg/MTU3OTM2ODc3OTM4Nw==579368779387'
  download(url)
})
1
2
3
4

总结一下就是使用URL.createObjectURL(blob)为资源的创建一个临时的链接进行下载

浏览器会直接将这资源下载到本地,不会进行预览

# 发现的缺陷

# 大文件需要等待长时间

由于使用了xhr实现,在xhr.onload是在资源完全响应后才会触发

这就存在一个问题,如果文件非常大,那么需要等待很久,才会执行下载(直接完成)

用户无法感知到这个下载过程,会以为下载没生效(传统的下载会由浏览器接管,所以用户能看到进度),疯狂的连续多次的点击

我们需要监听下载的进度,用于提醒用户,下面拓展一下上面的download方法:

  • 监听onprogress方法
  • 获取total与loaded这两个属性
  • 相除得到下载进度
function download(url, name) {
  var xhr = new XMLHttpRequest()
  xhr.open('GET', url)
  xhr.responseType = 'blob'

  // 增加的代码
  xhr.onprogress = function (e) {
    const { total,loaded } = e
    const percentage = (loaded/total).toFixed(2)
    console.log('progress:', percentage);
  }
  
  xhr.onload = function () {
    // 省略...跟前面的代码一致
  }
  xhr.send()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

示例

找了一个稍微大一点的图片进行测试,可以发现在网速慢的时候还是需要一定的时间才能完成下载

download('https://img.cdn.sugarat.top/mdImg/MTYyMzA3NjA4NDQ4NA==desktop.jpg')
1

# 总结

  • 适合解决下载图片,PDF等文件被浏览器直接打开的情况
  • 针对比较大的资源,可以改造一下,反馈下载进度,提升用户体验

偶尔遇到一些工具库,源代码不多的话,可以花些时间看看,精炼精炼,然后变成自己的东西

Edit this page (opens new window)
Last Updated: 2022/5/15 12:46:34