前端二进制-实战

153 阅读8分钟

二进制对象

ArrayBuffer TypedArray DataView Blob Object Url

ArrayBuffer

  • ArrayBuffer对象用来表示通用的,固定长度的原始二进制缓冲区。
  • 他是一个字节数组,通常在其它语言中称为byte array
  • 不能直接操作ArrayBuffer的内容,而是要通过类型数组对象(TypedArray)或者DataView对象来操作。他们会将缓区中的数据表示为特定的格式,并通过这些格式来读取内容。
const buffer = new ArrayBuffer(8) // 8个字节,64位
console.log(buffer.byteLength);不能直接操做

操作ArrayBuffer的对象

TypedArray, DataView

TypedArray

  • TypedArray对象描述了一个底层的二进制缓存区的一个类数组视图
  • 它本身无法被实例化,甚至无法访问,可以把他理解为接口,他有很多的实现。
  • Int8Array 8位二进制有符号整数 -128 - 127 每单位1个bit
  • Unit7Array 8位无符号整数 0-255 每单位1个bit
  • Int16Array 16位二进制有符号整数 -32768 - 32767 每单位2个bit
  • Unit16Array 16位无符号整数 0 - 65535 每单位2个bit
const buffer = new ArrayBuffer(8)
const int8Array = new Int8Array(buffer)
const int16Array = new Int16Array(buffer)
console.log(int8Array .buffer);
console.log(int8Array.length);
console.log(int16Array.length);
// 
8 8 4
因为Int16Array每个单位是两个字节,所以八个字节就有四个单位,所以长度是4 

ArrayBuffer无法直接操作,可以通过Int8Array等类来操作。

DataView(任意读取多种数值类型)

  • DataView视图是一个可以从二进制ArrayBuffer对象中读写多种数值类型的底层接口
  • setInt8()从DataView起始位置以byte为计数的指定偏移量(byteOffset)处存储了一共8-bit数
  • getInt8()从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取了一共8-bit数
const buffer = new ArrayBuffer(2) //设置一个2字节的二进制对象
const dataView = new DataView(buffer)
console.log(dataView.buffer)
dataView.setInt8(0, 2) //从0开始,设置了一个值为2,结果应该是
dataView.setInt8(1,3) //从1个字节开始,设置了3
console.log(dataView.getInt8(0)); //2
console.log(dataView.getInt8(1));  // 3
console.log(dataView.getInt16(0)); // 515
//因为
200000010
300000011
结合起来就是 00000010  00000011 这样一个二进制数就是515

三者关系

DataView => dataView.buffer => ArrayBuffer
ArrayBuffer => new DataView(buffer) => DataView

TypedArray => new TypedArray(buffer) =>ArrayBuffer
ArryaBuffer => typedArray.buffer => TypedArray

Blob

  • Blob对象表示一个不可变的,原始数据类型的类文件对象,Blob表示的不一定是js原生格式的数据,
  • Fibe接口基于Blob,继承了Blob的功能并将其扩展使其支持用
const debug = {name: '123'}
let str = JSON.stringify(debug)
const blob = new Blob([str],{type: 'application/json'})
console.log(blob);

// 
{
size: 14
type: "application/json"
}

// 转换ArrayBuffer
const blob = new Blob([buffer],{type: 'application/json'})
console.log(blob);
{
size: 2
type: "application/json"
}
  • Blob()构造函数返会一个新的Blob对象,Blob的内容由参数数组中给出的值串联组成。
  • FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件的内容。使用File或者Blob对象指定要读取的文件或数据
    • readAsText()读取文本文件,返回文本字符串,默认编码使utf-8
    • readAsDataURL():读取文本获取一段以data开头的字符串,这段字符串的本质就是DataURL,DataURL是一种将文件嵌入到文档的方案,DataURL使将资源转为base64编码的字符串形式,并将这些内容存储到url中。
  • 一个blob对象可以被读取成多个类型。
  • 构造函数 const blob = new Blob(array,options)
  • array可以是一个由ArrayBuffer,ArrayBufferView,Blob,DomString等对象构成的Array,或者其他类似对象的混合体,它将会被放入Blob,DOMstring会被编写成utf-8
  • options是一个可选的BlobPropertyBag字典,type默认值是'',他代表了将会被放入到blob中的数组内容的MIME类型。
  • 例子:
const buffer = new ArrayBuffer(2); //设置一个2字节的二进制对象
const blob = new Blob([buffer], { type: "application/json" });
console.log(blob);

//一个blob可以被读成多个类型
function readBlob(blob, type) {
  return new Promise(function (resolve) {
  // fileReader可以读取存储在用户计算机的文件的内容。
    const reader = new FileReader();
    reader.onload = (e) => {
      resolve(e.target?.result);
    };
    switch (type) {
      case "ArrayBuffer":
        reader.readAsArrayBuffer(blob);
        break;
      case "DataURL": // 二进制数据转为base64
        reader.readAsDataURL(blob);
        break;
      case "Text":
        reader.readAsText(blob, "utf-8");
        break;
      default:
        break;
    }
  });
}
readBlob(blob, "ArrayBuffer").then((res) => {
  console.log(res);
});
readBlob(blob, "DataURL").then((res) => {
  console.log(res);
});
readBlob(blob, "Text").then((res) => {
  console.log(res);
});


//
Blob {size: 2, type: 'application/json'}
ArrayBuffer(2) // ArrayBuffer类型
data:application/json;base64,AAA= //DataURL类型
空字符串  //Text类型
总结

FIleReader可以读取File或者Blob对象,将其转换成对应的数据,如DataUrl, ArrayBuffer和text等等。 比如input上传的图片是Blob对象,可以通过FileReader转为DataUrl,然后给img标签使用就可以。

// react
 const [imgUrl, setImgUrl] = useState<string>();
 const onInputChnge = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files ? e.target.files[0] : null; //input上传的图片是Blob对象
    console.log(file instanceof Blob);
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file!); //转为地址
    fileReader.onload = (e) => {
      console.log(e.target?.result);
      setImgUrl(e.target?.result as string);
    };
  };
// html
<input type="file" accept="image/*" onChange={onInputChnge} />
<img src={imgUrl} alt="" />

Object URL

  • 可以使用浏览器新的API URL对象通过一个方法生成一个地址来表示Blob数据。
  • 格式为 blob:< origin >/< uuid >
  • URL.creatObjectURL静态方法会创建一个DOMString,其中包含一个表示参数中给出的对象的URL,这个URL的生命周期喝创建他的窗口中的document绑定。
  • URL对象表示指定的FIle对象或者Blob对象。
  • revokeObjectURL静态方法用来释放一个值存在的,通过调用URL.createObjectURL()创建的URL对象。 如
 <button onclick="download()">下载</button>
    <script>

        const debug = {name: '123'}
        let str = JSON.stringify(debug)

        const blob = new Blob([str],{type: 'application/json'})
        console.log(blob);
        function download(){
             const a = document.createElement('a')
             a.download = 'user.json'
             a.rel = 'noopener'
             a.href = URL.createObjectURL(blob)
             console.log(a);
             //<a download="user.json" rel="noopener" href="blob:null/b0ffdc55-b049-492c-ad92-ac667b27d3ba"></a>
             // 触发a的点击事件
             a.dispatchEvent(new MouseEvent('click'))
             URL.revokeObjectURL(blob) //用于垃圾回收,销毁
        }
    </script>

通过Object.createObjectUrl将blob对象转为一个地址,可以看到href返回了一个以blob:开头的地址, 打开浏览器,点击下载之后,下载了一个文件。

user.json
{"name":"123"}

Blob对象通过Url.createObjectURL创建了url,可以给image使用。

Canvas & DataURl & imageData & Blob/FIle

File类型转为DataUrl,再转为Canvas可以使用的,再截取canvas的数据,通过Canvas.toDataURL()转为DataUrl,最后再转为blob对象,可以上传服务器。

FIle转为DataUrl,借助FileReader
// 通过input事件
  const onInputChnge = (e: React.ChangeEvent<HTMLInputElement>) => {
    setlastPosition({ startX: 0, startY: 0 });
    setPosition({ startX: 0, startY: 0 });
    const file = e.target.files ? e.target.files[0] : null; //input上传的图片是Blob对象
    console.log(file instanceof Blob);
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file!); //转为地址
    fileReader.onload = (e) => {
      console.log(e.target?.result); // 这就是转化后的url
      setImgUrl(e.target?.result as string);
     
    };
  };
//将图片绘制到canvas
 // 将图片绘制到canvas
  const drawImage = (scale1: any = 1, left = 0, top = 0) => {
    if (imgRef.current && canvasRef.current) {
      const image = imgRef.current;
      let canvas = canvasRef.current;
      const ctx = canvas?.getContext("2d");
      // 清掉canvas
      ctx?.clearRect(0, 0, canvas.width, canvas.height);
      //
      let imageWidht = image.width;
      let imageHieght = image.height;
      if (imageWidht > imageHieght) {
        let scale = canvas.width / imageWidht;
        imageWidht = canvas.width;
        imageHieght = imageHieght * scale;
      } else {
        let scale = canvas.height / imageHieght;
        imageHieght = canvas.height;
        imageWidht = imageWidht * scale;
      }
      requestAnimationFrame(() => {
        ctx?.drawImage(
          image, // Img元素
          (300 - imageWidht) / 2 + left,
          (300 - imageHieght) / 2 + top,
          imageWidht,
          imageHieght
        );
      });
    }
  };

直接通过ctx.drwaImage将元素传进去就行。 canvas数据转为DataUrl

// 截图, canvas数据可以通过toDataURL转为url
  const confirm = (e) => {
    const canvas = canvasRef.current;
    const ctx = canvas?.getContext("2d");
    // 截取,xy坐标+宽高
    const imageData = ctx?.getImageData(100, 100, 100, 100);
    const avatarCanvas = document.createElement("canvas");
    avatarCanvas.width = 100;
    avatarCanvas.height = 100;
    const avatrCtx = avatarCanvas.getContext("2d");
    avatrCtx?.putImageData(imageData!, 0, 0);
    const dataURl = (avatarCanvas as any).toDataURL(); //转为url ,这个是关键
    setImgUrl1(dataURl);
  };

DataUrl转为Blob对象供上传 通过window.atob转化,然后通过ArrayBuffer和uInt8Array转化。

// 转为blob对象可供上传
  // atob()用于解码 base64编码的字符串
  // btoa()//将字符串编码成base64字符串
  const onUpload = (event) => {
    const bytes: string = window.atob(imgUrl1!.split(",")[1]);
    const arrayBuffer = new ArrayBuffer(bytes.length); //创建一个长度一样大的buffer数组
    const uInt8Array = new Uint8Array(arrayBuffer);
    for (let i = 0; i < bytes.length; i++) {
      uInt8Array[i] = bytes.charCodeAt[i];
    }
    const blob = new Blob([arrayBuffer], { type: "image/png" }); // 转为blob对象
    console.log("blob", blob);

	// 通过FileReader又将blob转为DataUrl
    const fileReader = new FileReader();
    fileReader.readAsDataURL(blob!); //转为地址
    fileReader.onload = (e) => {
      console.log(e.target?.result);
      setImgUrl2(e.target?.result as string);
    };
  };

这里实现的是一个上传图片并且截图头像的功能。所有代码如下,因demo,所以没有整理,随便写了下:

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "core-js/stable";
import "regenerator-runtime/runtime";
import { useSetRem } from "./utils";
import "./assets/css/init.less";
import { Button } from "antd";

// const buffer = new ArrayBuffer(2); //设置一个2字节的二进制对象
// const blob = new Blob([buffer], { type: "application/json" });
// console.log(blob);

// //一个blob可以被读成多个类型
// function readBlob(blob, type) {
//   return new Promise(function (resolve) {
//     const reader = new FileReader();
//     reader.onload = (e) => {
//       resolve(e.target?.result);
//     };
//     switch (type) {
//       case "ArrayBuffer":
//         reader.readAsArrayBuffer(blob);
//         break;
//       case "DataURL": // 二进制数据转为base64
//         reader.readAsDataURL(blob);
//         break;
//       case "Text":
//         reader.readAsText(blob, "utf-8");
//         break;
//       default:
//         break;
//     }
//   });
// }
// readBlob(blob, "ArrayBuffer").then((res) => {
//   console.log(res);
// });
// readBlob(blob, "DataURL").then((res) => {
//   console.log(res);
// });
// readBlob(blob, "Text").then((res) => {
//   console.log(res);
// });

const App = () => {
  useSetRem();
  const [size, setSize] = useState(1);
  const imgRef = React.useRef<HTMLImageElement>(null);
  const [imgUrl, setImgUrl] = useState<string>();
  const [imgUrl1, setImgUrl1] = useState<string>();
  const [imgUrl2, setImgUrl2] = useState<string>();
  const onInputChnge = (e: React.ChangeEvent<HTMLInputElement>) => {
    setlastPosition({ startX: 0, startY: 0 });
    setPosition({ startX: 0, startY: 0 });
    const file = e.target.files ? e.target.files[0] : null; //input上传的图片是Blob对象
    console.log(file instanceof Blob);
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file!); //转为地址
    fileReader.onload = (e) => {
      console.log(e.target?.result);
      setImgUrl(e.target?.result as string);
      imgRef.current?.addEventListener("load", () => drawImage(1));
    };
  };

  // 将图片绘制到canvas
  const drawImage = (scale1: any = 1, left = 0, top = 0) => {
    console.log(123123);
    if (imgRef.current && canvasRef.current) {
      const image = imgRef.current;
      let canvas = canvasRef.current;
      const ctx = canvas?.getContext("2d");
      // 清掉canvas
      ctx?.clearRect(0, 0, canvas.width, canvas.height);
      //
      let imageWidht = image.width;
      let imageHieght = image.height;
      if (imageWidht > imageHieght) {
        let scale = canvas.width / imageWidht;
        imageWidht = canvas.width;
        imageHieght = imageHieght * scale;
      } else {
        let scale = canvas.height / imageHieght;
        imageHieght = canvas.height;
        imageWidht = imageWidht * scale;
      }
      imageHieght = imageHieght * Number(scale1);
      imageWidht = imageWidht * Number(scale1);
      console.log("top", top);
      console.log("left", left);

      console.log("imageWidht", imageWidht);

      requestAnimationFrame(() => {
        ctx?.drawImage(
          image,
          (300 - imageWidht) / 2 + left,
          (300 - imageHieght) / 2 + top,
          imageWidht,
          imageHieght
        );
      });
    }
  };

  const canvasRef = React.useRef<HTMLCanvasElement>(null);

  const position = React.useRef({ startX: 0, startY: 0 });
  const setPosition = (e) => {
    position.current = e;
  };
  const lastPosition = React.useRef({ startX: 0, startY: 0 });
  const setlastPosition = (e) => {
    lastPosition.current = e;
  };
  const Drag = React.useRef(false);
  // 按下
  const handleMouseDown = (e) => {
    console.log("lastPosition", lastPosition);
    setPosition({
      startX: e.clientX,
      startY: e.clientY,
    });
    Drag.current = true;
  };

  // 移动
  const handleMouseMove = (e) => {
    if (Drag.current) {
      // x方向移动的量, y方向移动的量
      drawImage(
        size,
        e.clientX - position.current.startX + lastPosition.current.startX,
        e.clientY - position.current.startY + lastPosition.current.startY
      );
    }
  };

  // 抬起
  const handleMouseUp = (e) => {
    if (Drag.current) {
      setlastPosition({
        startX:
          e.clientX - position.current.startX + lastPosition.current.startX,
        startY:
          e.clientY - position.current.startY + lastPosition.current.startY,
      });
      Drag.current = false;
    }
  };

  // 截图, canvas数据可以通过toDataURL转为url
  const confirm = (e) => {
    const canvas = canvasRef.current;
    const ctx = canvas?.getContext("2d");
    // 截取,xy坐标+宽高
    const imageData = ctx?.getImageData(100, 100, 100, 100);
    const avatarCanvas = document.createElement("canvas");
    avatarCanvas.width = 100;
    avatarCanvas.height = 100;
    const avatrCtx = avatarCanvas.getContext("2d");
    avatrCtx?.putImageData(imageData!, 0, 0);
    const dataURl = (avatarCanvas as any).toDataURL(); //转为url
    setImgUrl1(dataURl);
  };

  // 转为blob对象可供上传
  // atob()用于解码 base64编码的字符串
  // btoa()//将字符串编码成base64字符串
  const onUpload = (event) => {
    const bytes: string = window.atob(imgUrl1!.split(",")[1]);
    const arrayBuffer = new ArrayBuffer(bytes.length); //创建一个长度一样大的buffer数组
    const uInt8Array = new Uint8Array(arrayBuffer);
    for (let i = 0; i < bytes.length; i++) {
      uInt8Array[i] = bytes.charCodeAt[i];
    }
    const blob = new Blob([arrayBuffer], { type: "image/png" }); // 转为blob对象
    console.log("blob", blob);

    const fileReader = new FileReader();
    fileReader.readAsDataURL(blob!); //转为地址
    fileReader.onload = (e) => {
      console.log(e.target?.result);
      setImgUrl2(e.target?.result as string);
    };
  };

  return (
    <div style={{ display: "flex" }}>
      <div style={{ flex: 1 }}>
        <input type="file" accept="image/*" onChange={onInputChnge} />
        <img ref={imgRef} src={imgUrl} alt="" />
      </div>
      <div
        style={{ flex: 1 }}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUp}
      >
        <div style={{ position: "relative" }}>
          <canvas
            ref={canvasRef}
            width="300px"
            height="300px"
            style={{ border: "2px dashed blue" }}
          ></canvas>
          <div
            style={{
              width: 100,
              height: 100,
              background: "yellow",
              opacity: 0.3,
              position: "absolute",
              top: 100,
              left: 100,
            }}
          ></div>
        </div>
        <div>
          <Button
            onClick={() => {
              setSize((size * 5) / 4);
              console.log(lastPosition);

              drawImage(
                (size * 5) / 4,
                lastPosition.current.startX,
                lastPosition.current.startY
              );
            }}
          >
            放大
          </Button>
          <Button
            onClick={() => {
              setSize((size * 3) / 4);
              drawImage(
                (size * 3) / 4,
                lastPosition.current.startX,
                lastPosition.current.startY
              );
            }}
            style={{ margin: "0 10px" }}
          >
            缩小
          </Button>
          <Button onClick={confirm}>截取</Button>
        </div>
      </div>
      <div style={{ flex: 1 }}>
        <img src={imgUrl1} alt="" />
        <Button onClick={onUpload}>上传</Button>
        <img src={imgUrl2} alt="" />
      </div>
    </div>
  );
};

ReactDOM.render(<App></App>, document.getElementById("root"));

效果:

123.gif

总结

在这里插入图片描述

  • 二进制对象有ArrayBuffer,但他一般不可以直接操作,可以通过TypedArray或者DataView操作。
  • ArrayBuffer可以转为Blob对象,File对象基于Blob,一般Blob/File是用来上传图片的类型,Blob对象可以通过URL.createObjectURL(blob)转为ObjectUrl,或者通过fileReader.readAsDataURL转为base64编码,给img标签使用。
  • img元素可以直接给canvas绘制,canvas通过getImageData获取canvas的部分数据。又可以通过canvas.putImageData将这部分数据重新传给canvas绘制
  • canvas可以通过canvas.toDataUrl转为base64编码
  • 通过window.btoa()和二进制对象,可以将base64转为Blob对象供上传。