图片二进制流如何在页面显示?

最近工作中遇到后台接口返回图片二进制流,需要前端在页面上正常显示出图片的需求,网上也介绍了多种方法解决这个问题。今天还是想总结下这个问题的解决方案及涉及到的知识点

方案一:在 axios 请求中通过设置responseType:arraybuffer,将二进制流转为 arrayBuffer,最终转为 base64 在页面显示
方案二:在 axios 请求中通过设置responseType:blob,将二进制流转为 blob 对象,最终通过 URL.createObjectURL(blob)转为 DOMString 链接在页面显示

responseType 属性介绍

axios 官网中有关于 responseType 的介绍

// `responseType` 表示浏览器将要响应的数据类型
// 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
// 浏览器专属:'blob'
responseType: 'json', // 默认值

axios 设置 responseType:arraybuffer 属性,xhr 请求发起后,浏览器会按照设置的类型进行解析和处理响应数据。

通过 charles 抓包 http 响应结果发现,不同的 responseType 属性 http 响应结果都是一样的,说明 responseType 属性不会影响 http 响应,是浏览器按照设置的类型进行解析和处理响应数据。

通过 ArrayBuffer 转 base64 来解决

关键代码

const url =
  "https://img0.baidu.com/it/u=73689209,3130028231&fm=253&fmt=auto&app=138&f=JPEG";

axios
  .get(url, {
    responseType: "arraybuffer",
  })
  .then((res) => {
    let imgBase64 =
      "data:image/png;base64" +
      window.btoa(String.fromCharCode(...new Uint8Array(res.data)));
    const imgDom = document.getElementById("img");
    imgDom.src = imgBase64;
  });

响应的 ArrayBuffer

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。它是一个字节数组,通常在其他语言中称为“byte array”。
你不能直接操作 ArrayBuffer 中的内容;而是要通过 TypedArray(类型数组对象)或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

TypedArray 对象描述了底层二进制数据缓冲区的类数组视图。没有称为 TypedArray 的全局属性,也没有直接可用的 TypedArray 构造函数。但是有很多不同的全局属性,其值是指定元素类型的类型化数组构造函数

实际上,TypedArray 指的是以下的其中之一:

  • Int8Array();
  • Uint8Array();
  • Uint8ClampedArray();
  • Int16Array();
  • Uint16Array();
  • Int32Array();
  • Uint32Array();
  • Float32Array();
  • Float64Array();

以 Uint8Array 为例,它可以接受 buffer 参数,此时一个新的类型化数组视图将会被创建,可用于呈现传入的 ArrayBuffer 对象。

String.fromCharCode(...new Uint8Array(result));
就是将我们传入的类型化数组视图转为字符串,按照 UTF-16 的方式

window.btoa

btoa() 方法可以将一个二进制字符串(例如,将字符串中的每一个字节都视为一个二进制数据字节)编码为 Base64 编码的 ASCII 字符串。

btoa()的作用是编码为 base64,atob()的作用是对 base64 字符串进行解码。

window.btoa 将每个字符视为二进制数据的字节,而不管实际组成字符的字节数是多少,所以字符的码位不能超出 0x00 ~ 0xFF 范围,也就是十进制 0 ~ 255,这也是我们在上面用 Uint8Array 的原因

因此 window.btoa 会将上面转换的普通字符串转为 base64,最后在前面加上 data:image/png;base64,就可以正常显示图片了

通过 Blob 来解决

关键代码

const url =
  "https://img0.baidu.com/it/u=73689209,3130028231&fm=253&fmt=auto&app=138&f=JPEG";
axios
  .get(url, {
    responseType: "blob",
  })
  .then((res) => {
    const objectURL = window.URL.createObjectURL(res.data);
    const imgDom = document.getElementById("img");
    imgDom.src = objectURL;
    img.onload = function () {
      // 调用 URL.revokeObjectURL() 方法来释放这个对象
      window.URL.revokeObjectURL(objectURL);
    };
  });

响应的 Blob

Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。

URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。

URL.revokeObjectURL() 静态方法用来释放一个之前已经存在的、通过调用 URL.createObjectURL() 创建的 URL 对象。当你结束使用某个 URL 对象之后,应该通过调用这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。

最后项目中采用方案二,因为当图片较大时会产生大量的字符

接口报错处理

在项目中当没有图片返回或图片获取失败时,需要展示出失败的原因,而此时我们已经将请求结果转化成了 Blob 或者 ArrayBuffer,此时我们需要转化成 json 格式才能正确显示失败的原因

responseType 为 arraybuffer 时

axios
  .get(url, {
    responseType: "arraybuffer",
  })
  .then((res) => {
    try {
      const uint8arr = new Uint8Array(res.data)
      const utf8decoder = new TextDecoder()
      const temp = utf8decoder.decode(uint8arr)  // 将二进制数据转为字符串
      const msg = JSON.parse(temp)    //这一步报错则返回的是二进制流图片,不报错则返回的是JSON的错误提示数据
      console.log(msg)   //msg为转换后的JSON数据
    } catch () {
      let imgBase64 =
        "data:image/png;base64" +
        window.btoa(String.fromCharCode(...new Uint8Array(res.data)));
      const imgDom = document.getElementById("img");
      imgDom.src = imgBase64;
    }
  });

TextDecoder.decode() 方法返回一个字符串,其包含作为参数传递的缓冲区解码后的文本。

decode();
decode(buffer);
decode(buffer, options);

buffer 可选 一个 ArrayBuffer、TypedArray 或包含要解码的编码文本的 DataView 对象。

responseType 为 blob 时

axios
  .get(url, {
    responseType: "blob",
  })
  .then((res) => {
    const reader = new FileReader(); //创建一个FileReader实例
    reader.readAsText(res.data, "utf-8"); //读取文件,结果用字符串形式表示
    reader.onload = () => {
      //读取完成后,获取reader.result
      try {
        const msg = JSON.parse(reader.result); //这一步报错则返回的是二进制流图片,不报错则返回的是JSON的错误提示数据
        console.log(msg); //msg为转换后的JSON数据
      } catch {
        const objectURL = window.URL.createObjectURL(res.data);
        const imgDom = document.getElementById("img");
        imgDom.src = objectURL;
        img.onload = function () {
          // 调用 URL.revokeObjectURL() 方法来释放这个对象
          window.URL.revokeObjectURL(objectURL);
        };
      }
    };
  });

至此完美展示接口错误提示

二进制文件流下载

二进制文件流下载时我们一般直接设置 responseType:blob

axios
  .get(url, {
    responseType: "blob",
  })
  .then((res) => {
    const { data, headers } = res;
    //获取文件流及文件类型
    const blob = new Blob([data], { type: headers["content-type"] });
    let a = document.createElement("a");
    let url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = "最美日落.jpeg";
    a.style.display = "none";
    document.body.appendChild(a);
    a.click();
    a.remove(a);
    window.URL.revokeObjectURL(url);
  });

总结

图片二进制流在前端处理时优先考虑使用 Blob 格式

  1. 设置 responseType:“blob”
  2. 通过 URL.createObjectURL(blob)创建一个 DOMString
  3. 用完后使用 URL.revokeObjectURL()释放内存

参考文章:

  1. axios 官网
  2. ArrayBuffer,TypedArray,Blob,TextDecoder
  3. js 将图片二进制流转为 base64 显示
  4. 你知道前端对图片的处理方式吗?