十字 发布的文章

前端下载文件的几种方式

阶段性总结一下前端可以用于文件下载的几种方法

1. a[href=*] 右键另存为

最原始的方式之一,在很多远古的网站上会存在,一般需要搭配后端对文件请求的 reponseHeader 设置 Content-Disposition ,否则用户直接点击的话可能会打开预览而不是文件下载。

如果服务器实现了 Content-Disposition 的话,前端也可以使用这些方式直接去打开对应的url:

  • location.href
  • window.open
  • iframe[src]
  • a[href].click

2. a[download='filename'] 使用 download 属性

现代 HTML5 规范提供了一种直接在链接声明文件可下载的方式,使用 a 标签的 download 属性,属性值将会作为下载文件的文件名,使用这个方法,结合 ObjectUrl,首次将前端生成文件并直接在网页上触发下载变成了可能。

在这个API的基础上终于能够做到直接在前端生成并下载 文本文件、excel 等文件,而无需后端服务参与。

上述两种方式都有一个比较基础和典型的实现:https://github.com/eligrey/FileSaver.js

3. ServiceWorker

ServiceWorker 对于全局请求的劫持,结合 Stream API, 使前端流式创建/下载文件变成了可能,在前端技术栈上可以做到流式下载一个超大文件,例如几个 G 大小的视频。

一个比较经典的实现来自: https://github.com/jimmywarting/StreamSaver.js

4. showSaveFilePicker

Chrome>86 的才可以使用的一个API,同样可以用于保存文件,属于 浏览器 FileSystemApi 的一部分,同样可以流式写入,缺点是不会产生浏览器原生的下载进度和记录,需要自己实现下载进度的相关交互界面。

QQ音乐-付费音乐包开通办法

截至 2023年10月11日 , QQ Music 已经下线了包括 web、移动端在内的所有 付费音乐包 的开通链接(仅当前处于音乐包套餐的用户能通过移动端续费)。

新用户无法购买 8 元的音乐包,绿钻用户也无法单独续费音乐包。

不过经过研究,目前尚有方法能够拉起音乐包开通,且开且珍惜。

- 阅读剩余部分 -

使用 SVG 实现 Canvas 响应式缩放

很多时候我们需要做一个响应式的页面,如果页面中存在一个Canvas,调整尺寸的时候一般就需要重绘它。

在大部分情况下,操作 Canvas 并重绘并非不可接受,但是在一些场景中,我们既需要保持高分辨率的 Canvas,又需要它能适应屏幕的宽度,比如我们创建了一个 Canvas 用于捕捉屏幕上的视频流或者图片流,并且可能启动了一个 MediaRecorder 录制该 Canvas,在这个时候,强行修改Canvas的尺寸会影响正在录制的 MediaRecorder ,即便不在录制过程中,也会降低画面的分辨率,那么有什么其他方法吗?

做法一

使用一个新的 Canvas 来进行渲染,与原本的录制 Canvas 隔离。

我们可以将录制或者相关的业务逻辑放在另一个 Canvas中,可能是 offscreenCanvas。
然后我们在 requestAnimationFrame 或类似的时机,将 offscreenCanvas的内容绘制到实际显示的 Canvas 中。

这个方法的优势在于整体的数据流向不会变,缺点是额外绘制一个 Canvas 总是一种性能损耗,并且对上屏的 Canvas,我们依然要去监听 resize 并且重设 Canvas 的宽高。

做法二

仔细回顾一下我们的需求,如果要做到最完美的版本,即:我们需要有一个方案,他能够让 Canvas 全尺寸绘制并且不影响所有Canvas 相关的API,与此同时,它还要支持 CSS 的响应式逻辑。

没错,SVG。

SVG能够在 DOM 中创造一块独立的渲染逻辑,其中由 SVG 标签的 viewBOX 定义了整个渲染区域的坐标和尺寸,而在 DOM 中,它就像一个图片一样,可以使用 object-fit 或者 width 等 CSS 属性来实际控制它的尺寸和渲染方式。

于是在一个 Vue Template 中,我们可以这样写一个 Canvas:

<svg :viewBox="`0 0 ${loadedArea?.width} ${loadedArea?.height}`" xmlns="http://www.w3.org/2000/svg" class="w-full">
  <foreignObject x="0" y="0" :width="loadedArea?.width" :height="loadedArea?.height">
    <canvas ref="ImageCanvas" :height="loadedArea?.height" :width="loadedArea?.width" class="canvas"></canvas>
  </foreignObject>
</svg>
/src/tools/radar-chart/index.vue#L323-L328

使用 foreignObject ,并且把 SVG 的 viewbox 设成和 Canvas 相同的尺寸,就可以使 Canvas 完全铺满 SVG 的渲染空间。

最后,原有的逻辑完全不需要修改,依然可以通过 JS 去操作 Canvas的 API ,可以说是一种比较完美的解决方法。