为什么大文件预览要用 Blob URL?真相终于被我扒出来了!

内容分享3周前发布
1 2 0

大家好,很高兴又见面了,我是”高级前端‬进阶‬”,由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。

1. 什么是 Blob URL

在 Web 开发中,常常会遇到需要直接在浏览器中处理数据而不通过服务器。此时,Blob URL 就派上用场了。其是一种强劲的客户端数据处理机制,能够提供灵活性和性能优势。

那么,Blob URL 究竟是什么呢?

为什么大文件预览要用 Blob URL?真相终于被我扒出来了!

其表明以二进制大对象(BLOB)形式存储在浏览器中的数据,开发者也可以将其看做是存储在浏览器内存中的文件,Blob URL 本质上是指向该文件的临时链接。开发者也可以将其视为指向原始数据的指针,无论是图像、视频、音频还是任何其他类型的文件,这类 URL 都以 blob: 开头。

Blob URL 具有以下几个显著优势:

  • 直接通过客户端数据处理:允许开发者直接在浏览器中处理数据而无需将其上传到服务器,在文件预览、动态内容创建(例如:从 Canvas 创建图片)、离线处理等场景超级有用
  • 性能提升:通过避免不必要的服务器往返,Blob URL 可以显著提升 Web 应用程序的性能。开发者无需与服务器来回发送数据,而是直接在本地操作
  • 降低服务器负载:将数据处理转移到客户端可以大大减轻服务器负载,提高应用程序的可扩展性
  • 增强用户体验:即时预览和离线访问等功能可带来更流畅、响应更快的用户体验

同时,值得一提的是,Blob URL 还具有以下特性:

  • 临时性:Blob URL 是临时的,其仅在创建的页面处于打开状态时存在。如果用户关闭或刷新页面,Blob URL 会自动失效
  • 作用域:Blob URL 只能在创建的浏览器上下文中访问,不能在不同的浏览器标签页或网站之间共享
  • 内存管理:由于 Blob URL 表明存储在内存中的数据,因此谨慎管理至关重大。使用完 Blob URL 后,应使用 URL.revokeObjectURL() 释放关联的内存,从而防止内存泄漏
document.getElementById("downloadBtn").addEventListener("click", () => {
  // 创建一个 Blob 对象(模拟文件内容)
  const content = "这是一个由 Blob 生成的文本文件内容";
  const blob = new Blob([content], { type: "text/plain" });
  // 创建一个对象 URL
  const objectURL = URL.createObjectURL(blob);
  // 创建 <a> 元素用于下载
  const a = document.createElement("a");
  a.href = objectURL;
  a.download = "example.txt";
  // 指定下载文件名
  document.body.appendChild(a);
  a.click();
  // 下载完成后释放内存
  a.addEventListener("click", () => {
    document.body.removeChild(a);
    URL.revokeObjectURL(objectURL); // 释放对象 URL
    console.log("对象 URL 已释放");
  });
});

2.Blob URL 是如何工作的

创建 Blob 时,浏览器会自动获取开发者提供的数据,例如:可能来自文件上传、Canvas 元素或 API 响应的数据等,并将其存储在内存中。然后,浏览器会生成一个指向该数据的唯一 URL,该 URL 就是 Blob URL。

2.1 使用文件输入创建 Blob URL

下面示例使用文件输入创建 Blob URL:

const fileInput = document.getElementById("imageInput");
fileInput.addEventListener("change", (event) => {
  const file = event.target.files[0];
  // 获取选中的文件
  if (file) {
    const blob = new Blob([file], { type: file.type });
    // 创建 Blob
    const blobUrl = URL.createObjectURL(blob);
    // 创建 Blob URL 对象
    const imgElement = document.getElementById("imagePreview");
    imgElement.src = blobUrl;
    // 使用 blob 或者 blobUrl 实现业务逻辑
  }
});

2.2 使用 Uint8Array 二进制数据创建 Blob URL

开发者还可以使用 Uint8Array 格式的图像数据,例如:fetch 请求或 WebSocket 返回的数据,来创建 Blob:

fetch("image.jpg")
  .then((response) => response.arrayBuffer())
  .then((buffer) => {
    const uint8Array = new Uint8Array(buffer);
    const blob = new Blob([uint8Array], { type: "image/jpeg" });
    // 注意:要选中正确的类型
    const blobUrl = URL.createObjectURL(blob);
    const imgElement = document.getElementById("imagePreview");
    imgElement.src = blobUrl;
    // 使用 blob 或者 blobUrl 创建业务逻辑
  });

此时,开发者可以像使用其他 URL 一样使用此 Blob URL,例如:可以将其设置为 <img> 标签的 src 来显示图像或使用其来下载数据。

3. 使用 Blob URL 创建 Worker

开发者在创建 Worker 时常常会遇到下面的错误:

Uncaught (in promise) DOMEXception: Failed to construct 'Worker' (…) cannot be accessed from origin (…)

这是由于 Worker 脚本的创建必须遵循同源策略,即与主页面同源。此时,Blob URL 就派上用场了。下面示例使用 Blob URL 来动态创建 Worker,从而有效绕过同源策略:

const type = "application/javascript";

export default (originalWorkerUrl, _options = {}) => {
  const options = {
    skipSameOrigin: true,
    useBlob: true,
    ..._options,
  };
  if (
    !originalWorkerUrl.includes("://") ||
    originalWorkerUrl.includes(window.location.origin)
  ) {
    // 如果同源,直接 resolve 返回
    return Promise.resolve(originalWorkerUrl);
  }
  //  如果不同源则获取内容再创建 worker
  return new Promise((resolve, reject) =>
    fetch(originalWorkerUrl)
      .then((res) => res.text())
      .then((codeString) => {
        let workerPath = new URL(originalWorkerUrl).href.split("/");
        workerPath.pop();
        // 删除最后一级路径,例如:https://developer.mozilla.org/en-US/docs/Web
        // workerPath 将包含路径 https://developer.mozilla.org/en-US/docs
        const importScriptsFix = `const _importScripts = importScripts;
          const _fixImports = (url) => new URL(url, '${
            workerPath.join("/") + "/"
          }').href;
          importScripts = (...urls) => _importScripts(...urls.map(_fixImports));`;
        // 1.new URL 的第二个参数是 base 路径
        // 如果 url 是绝对 URL,则不会使用给定的 base 路径来创建结果 URL
        // 2.new URL("umd/comlink.js", 'https://somedoamin.com/public/').href;
        // 输出结果为 https://somedoamin.com/public/umd/comlink.js
        let finalURL =
          `data:${type},` + encodeURIComponent(importScriptsFix + codeString);
        // data URL(以 data: 格式为前缀的 URL)允许内容创建者在文档中
        // 内联嵌入小文件
        if (options.useBlob) {
          finalURL = URL.createObjectURL(
            new Blob([`importScripts("${finalURL}")`], { type })
          );
        }
        // 创建 Blob URL 对象
        resolve(finalURL);
      })
      .catch(reject)
  );
};

该方法的本质是通过自动获取 URL 脚本的内容,同时创建一个 URL Blob 作为新的 URL 地址完成,下面是使用该方法的示例:

import getCrossOriginWorkerURL from "crossoriginworker";
async function createWorker() {
  const workerURL = await getCrossOriginWorkerURL(
    "https://somedomain.com/lib/awesome.worker.js"
  );
  return new Worker(workerURL);
}
const myWorker = await createWorker();

参考资料

https://dev.to/harsh8088/blob-urls-explained-how-they-work-and-why-they-matter-302d

https://github.com/CezaryDanielNowak/CrossOriginWorker

https://developer.mozilla.org/en-US/docs/Web/API/URL/URL

https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data

© 版权声明

相关文章

2 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    安柒苒 读者

    掌握了创建Blob URL方法

    无记录
  • 头像
    威威猫 读者

    收藏了,感谢分享

    无记录