一个项目小亮点---图片压缩上传

6,819 阅读4分钟

最近在复习,觉得这个知识点蛮有意思,就学了一下,随手写一篇笔记。考虑范围有限,欢迎交流讨论。

出现场景:

有些需求需要用户上传照片,而上传图片可能太大,导致上传时间过长,用户体验下降,这时候可以考虑前端做好图片文件压缩,减轻服务器压力。


最终效果:

原本图片大小

a.png

获取到初始的base64文件大小(17MB)

O$S1}YMUBM4MQV_GWGK0ZS5.png

canvas操作后的大小(1.1MB)

D[8)KH1{@7YNPYV{]ZTX%}2.png


实现逻辑

b.png


重点指出

file文件转base64格式

az.png

FileReader.readAsDataURL

MDN上对它的解释:developer.mozilla.org/zh-CN/docs/…

readAsDataURL 方法会读取指定的 BlobFile 对象。读取操作完成的时候,readyState 会变成已完成DONE,并触发 [loadend (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/loadend_event "Currently only available in English (US)") 事件,同时 result 属性将包含一个data:URL格式的字符串(base64编码)以表示所读取文件的内容。


通过Canvas对Base64数据进行压缩

b.png

//   处理base64数据,通过canvas(toDataURL)进行压缩绘制,然后输出压缩后的base64图片数据
    // 图片最大宽度
    const MAX_WIDTH = 800;
    const compress = (base64, quality, mimeType) => {
      //mimeType 图片类型,例如 mimeType='image/png'
      console.log(
        "处理base64数据,通过canvas(toDataURL)进行压缩绘制,然后输出压缩后的base64图片数据"
      );
      let cvs = document.createElement("canvas");
      let img = document.createElement("img");
      // CORS 策略,canvas中image会存在跨域问题
      img.crossOrigin = "anonymous";
      return new Promise((resolve, reject) => {
        img.src = base64;
        // 图片偏移值
        let offetX = 0;
        img.onload = () => {
          if (img.width > MAX_WIDTH) {
            // 做适配
            cvs.width = MAX_WIDTH;
            cvs.height = (img.height * MAX_WIDTH) / img.width;
            offetX = (img.width - MAX_WIDTH) / 2;
          } else {
            cvs.width = img.width;
            cvs.height = img.height;
          }
          // 重点! 将图片插入画布并开始绘制
          let ctx = cvs
            .getContext("2d")
            .drawImage(img, 0, 0, cvs.width, cvs.height);

          let imageData = cvs.toDataURL(mimeType, quality);
          console.log("canvas图片压缩", imageData);
          resolve(imageData);
        };
      });
    };

补充说明:canvas中图片cors配置问题

尽管您可以在画布中使用未经 CORS 批准的图像,但这样做会污染画布。一旦画布被污染,您就不能再将数据拉出画布。例如,您不能再使用画布的 toBlob()、toDataURL() 或getImageData() 方法;这样做会引发安全错误。 这可以防止用户通过使用图像在未经许可的情况下从远程网站提取信息而暴露私人数据。

这里给出一篇相关讨论帖子 stackoverflow.com/questions/2…

Canvas.getContext("2d").drawImage(img,0,0,width,height)

MDN上的介绍:

developer.mozilla.org/zh-CN/docs/…

最重要的点:canvas.toDataURL(type, encoderOptions);

参数:

  • type 可选

图片格式,默认为 image/png

  • encoderOptions 可选

在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

返回值

包含data URLDOMString


Base64格式转Blob二进制格式

c.png

// base64数据转成blob文件流
    const converrBase64UrlToBlob = (base64, mimeType) => {
      //mimeType 图片类型,例如 mimeType='image/png'
      console.log("base64数据转成blob文件流");
      let bytes = window.atob(base64.split(",")[1]); //atob方法用于解码base64
      // 创建一个长度为 bytes.length 的 buffer(一个二进制文件), 它会分配一个 16 字节(byte)的连续内存空间,并用 0 进行预填充。
      let ab = new ArrayBuffer(bytes.length);

      // Uint8Array —— 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位)。这称为 “8 位无符号整数”。
      let ia = new Uint8Array(ab);
      for (let i = 0; i < bytes.length; i++) {
        // 更改里面的初始化内容
        ia[i] = bytes.charCodeAt(i);
      }
      // 创建blob格式数据,并传入二进制文件和文件原本类型
      let _blob = new Blob([ia], { type: mimeType });
      toImg(_blob);
      return _blob;
    };

ArrayBuffer

MDN上的描述:developer.mozilla.org/zh-CN/docs/…

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。

它是一个字节数组,通常在其他语言中称为“byte array”。


示例代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input id="fileInput" type="file" />
    <img id="img" src="" alt="" />
    <img id="blobImg" src="" alt="" />
  </body>
  <script>
    let fileId = document.getElementById("fileInput");
    let img = document.getElementById("img");
    fileId.onchange = function (e) {
      console.log(e);
      let file = e.target.files[0]; //file文件
      fileToBase64(file, 0.2);
    };

    // 将file文件通过FileReader转化为base64格式
    //FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File或 Blob对象指定要读取的文件或数据。

    const fileToBase64 = (file, quality) => {
      //图片对象      图片质量
      console.log("将file文件通过FileReader转化为base64格式");
      let fileReader = new FileReader();
      let type = file.type; //"image/png"
      // 对文件进行读取
      fileReader.readAsDataURL(file);
      fileReader.onload = function (e) {
        console.log("原始二进制字符串:", this.result.toString());
        compress(fileReader.result, quality, type);
      };
    };

    //   处理base64数据,通过canvas(toDataURL)进行压缩绘制,然后输出压缩后的base64图片数据
    // 图片最大宽度
    const MAX_WIDTH = 800;
    const compress = (base64, quality, mimeType) => {
      //mimeType 图片类型,例如 mimeType='image/png'
      console.log(
        "处理base64数据,通过canvas(toDataURL)进行压缩绘制,然后输出压缩后的base64图片数据"
      );
      let cvs = document.createElement("canvas");
      let img = document.createElement("img");
      // CORS 策略,会存在跨域问题
      img.crossOrigin = "anonymous";
      return new Promise((resolve, reject) => {
        img.src = base64;
        // 图片偏移值
        let offetX = 0;
        img.onload = () => {
          if (img.width > MAX_WIDTH) {
            // 做适配
            cvs.width = MAX_WIDTH;
            cvs.height = (img.height * MAX_WIDTH) / img.width;
            offetX = (img.width - MAX_WIDTH) / 2;
          } else {
            cvs.width = img.width;
            cvs.height = img.height;
          }
          // 重点! 将图片插入画布并开始绘制
          let ctx = cvs
            .getContext("2d")
            .drawImage(img, 0, 0, cvs.width, cvs.height);

          let imageData = cvs.toDataURL(mimeType, quality);
          console.log("canvas图片压缩", imageData);
          // 我想转成blob格式看看
          let blobData = converrVase64UrlToBlob(imageData, mimeType);
          console.log("我想转成blob格式看看", blobData);
          //利用FormData传输数据
          // let formData = new window.FormData();
          // formData.append("file", _blob);
          resolve(imageData);
        };
      });
    };

    // base64数据转成blob文件流
    const converrVase64UrlToBlob = (base64, mimeType) => {
      //mimeType 图片类型,例如 mimeType='image/png'
      console.log("base64数据转成blob文件流");
      let bytes = window.atob(base64.split(",")[1]); //atob方法用于解码base64
      // 创建一个长度为 bytes.length 的 buffer(一个二进制文件), 它会分配一个 16 字节(byte)的连续内存空间,并用 0 进行预填充。
      let ab = new ArrayBuffer(bytes.length);

      // Uint8Array —— 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位)。这称为 “8 位无符号整数”。
      let ia = new Uint8Array(ab);
      for (let i = 0; i < bytes.length; i++) {
        // 更改里面的初始化内容
        ia[i] = bytes.charCodeAt(i);
      }
      // 创建blob格式数据,并传入二进制文件和文件原本类型
      let _blob = new Blob([ia], { type: mimeType });
      toImg(_blob);
      return _blob;
    };

    // 尝试操作成功转化的blob文件
    // 利用createObjectURL转化为DataUrl格式
    const toImg = function (blobObj) {
      console.log("尝试操作成功转化的blob文件");
      // 两种方法都可以将Blob数据转化为DataUrl格式
      let imgSrc = window.URL.createObjectURL(blobObj);
      //let imgSrc = window.webkitURL.createObjectURL(blobObj);
      document.getElementById("blobImg").src = imgSrc;
    };
  </script>
</html>



参考文章: 前端图片压缩上传(压缩篇):可能是最适合小白的前端图片压缩文章了!

图片压缩上传


交个朋友(2022.03.20记)

目前正在疯狂学习前端知识,想要成为更优秀的前端工程师,因此喜欢记录并分享自己的学习笔记。奈何本人知识储备有限,只能做到输出一些些自己的观念。

不过!我真的有做很多很多笔记(确实很多笔记是摘抄而来,因此作为自己的帖子发出不厚道....),我也真的很希望能和志同道合的小伙伴们分享交流更多知识点!

因此我简单搭建了一个自己的博客,希望能结识到更多小伙伴,如果有兴趣,来我博客逛逛吧~ 阿敏的成长日记