• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

前端技术方向:浏览器进行文件保存的原理和方法,如何让浏览器执行下载

武飞扬头像
juejin
帮助1166

前言

用浏览器保存文件绝对是常见的 WEB 应用功能,就算你没有真正开发过,也一定会见过类似的场景。如下载合同WORK文件,保存编辑(生成)好的 PDF 文件,以及常见的 EXCEL 下载等等。但对于这些功能的实现你又了解多少?今天我们就来一场对浏览器保存的探索。

来自服务器的文件

相信大部分的人第一反应就是: 这不简单?只要前端直接访问一个文件URL就可以了。

当然,这是目前最广泛的使用方式。只要我们有一个返回文件的URL,然后前端直接访问,浏览器就会开始下载。

举个例子,这是一个 NodeJs 安装包的URL(nodejs.org/dist/v16.17…)。

然后我们创建一个HTML文件,在body部分写入一个a标签:

<body>
  <a href="https://nodejs.org/dist/v16.17.1/node-v16.17.1-x64.msi">下载</a>
</body>

然后运行这个HTML文件,点击下载字样,浏览器就会开始下载了。

为什么会下载?

现在,假如我们把HTML的内容改一下:

<body>
    <!-- <a href="https://nodejs.org/dist/v16.17.1/node-v16.17.1-x64.msi">下载</a> -->
    <a href="https://nodejs.org/en/">下载</a>
</body>
 

这次再运行HTML,点击a标签,你会发现浏览器跳转到了 href 中的地址,这里的例子是 NodeJs 的官网。

那么,为什么浏览器会有不一样的行为?它是怎么知道我们是需要下载,还是跳转的?这就需要提到 content-type。

content-type的意义

我们都是知道 HTTP 请求中资源包含了 headers 和 body 两个部分。其中 body 是资源本身的数据,而 headers 则是用于描述数据的信息。

当我们访问NodeJS官网的时候,可以通过开发者工具中的 Network 板块看到这些信息。

我们重点关注一下响应头,也就是 Response Headers,我们可以看到一个 content-type 字段,它的值是 text/html。

content-type 字段用于描述当前请求资源的类型,如上图中的 content-type 是 text/html 。 当浏览器识别到a标签访问的资源是 html 类型之后,就会开始渲染这个页面。那么如果我们把a标签的href换回NodeJS的安装包,content-type 又会是什么呢?

可以看到当浏览下载时,我们请求的资源响应头中的 content-type 的值是 application/octet-stream。当浏览器遇到此类资源时,会判断为 未知的应用程序文件 ,因此浏览器不会对此作出反应,而是触发下载。这样用户就可以自行执行。

下载的内容只能在服务器上吗?

到这里其实我们已经把浏览器下载的机制过了一遍了。但是如果我们想要绕开服务器,直接在用户本地浏览器上生成文件,然后通过浏览器的下载机制保存在用户本地。这可能实现吗?

生成可以访问的URL

要实现这一点,第一步,我们需要生成一个可以触发浏览器下载的URL。

相信有 canvas 开发经验的朋友,马上就能想到 URL.createObjectURL() 。没错它就是我们要找的目标,URL.createObjectURL() 可以根据一个 File 或者 Blob 对象生成一个可访问URL。

const url = window.URL.createObjectURL(file);

本地生成文件资源

有了解决 URL 的方案之后,我们要考虑的是,如何在本地生成文件。这里就要说到 Blob 类了。

用JS触发a标签点击事件

在正常情况下,用户只要点击a标签,浏览器就可以开始下载了。但在这个例子中,正式开始下载之前,我们还需要处理一些逻辑,如完善数据等。靠用户触发a标签肯定是来不及的,因此我们需要自己生成一个a标签,并模拟点击事件。当然这还有一个好处,就是除了a标签以为外,我们还可以用各种dom来实现下载的触发。

下面是这里的实现逻辑代码:

// 用 createElementNS 可以生成一个带命名空间的元素
// 这里用 http://www.w3.org/1999/xhtml 作命名空间是为了浏览器的兼容
const a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
// 下载文件的名称
a.download = '生成资源';
// 要下载的文件的URL
a.href = 'xxxxx';
// 用 dispatchEvent 触发 a 的 click 事件
a.dispatchEvent(new MouseEvent('click'));

综合代码

现在我们结合上面提到的知识点,整理一段例子代码,代码的逻辑就是用户可以点击一个 div 按钮,然后下载一个我们在本地生成的名为 hello.txt 的文件,文件内容是字符 "hello world!"。

<body>
  <div id="btn">点击下载</div>
  <script>
    const $btn = document.querySelector('#btn');

    const startDownload = function () {
      // 生成内容为 hello world! 的文件
      const blob = new Blob(['Hello, world!'], {
        type: 'text/plain;charset=utf-8',
      });
      //  触发click事件
      const click = (node) => {
        node.dispatchEvent(new MouseEvent('click'));
      };
      // 生成 a 标签
      var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
      a.download = 'hello.txt';
      // 根据 blob 生成可访问 url
      a.href = URL.createObjectURL(blob);

      // 异步事件 触发click
      setTimeout(function () {
        click(a);
      }, 0);

      // 这里再加一个异步事件,目的是用 URL.revokeObjectURL 解绑前面生成的url,释放内存。
      setTimeout(function () {
        URL.revokeObjectURL(a.href);
      }, 4e4);
    };

    $btn.addEventListener('click', startDownload);
  </script>
</body>

执行HTML文件后,点击 dom 按钮即可触发下载。

打开下载的文件,可以看到内容就是字符 "hello world!"。

总结

今天我们了解了浏览器的下载机制,并利用这个思路实现了不依赖服务器的浏览器本地下载。

本地下载其实是一个非常有用的功能,但遇到一些稍大一点的文件,如excel,word等。利用本地下载可以降低网络请求的耗时,极大地提高用户体验。过去服务器需要返回一个完整的文件,但结合本地下载,服务端只需把文件中的关键内容返回,然后前端根据这些内容生成文件即可。

由于下载在本地进行,因此不会有网络速度的依赖,效果显著。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanefjj
系列文章
更多 icon
同类精品
更多 icon
继续加载