前端二进制.png

基础对象

ArrayBuffer

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,它是一个字节数组,在其他语言中通常称为 byte array。

ArrayBuffer 不能直接操作,而是通过 TypedArrayDataView 来操作,它们在缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

  1. // 创建一个 8 个字节长度的 buffer,ArrayBuffer 的初始化必须固定长度
  2. const buffer = new ArrayBuffer(8)
  3. console.log(buffer.byteLength);

TypedArray

TypedArray 对象描述了一个底层二进制数据缓冲区的一个类数组视图,但它本身不可以被实例化,甚至无法访问。可以把 TypedArray 理解为接口或者基类,有很多的实现。

类型 单个元素值范围 大小
bytes
描述
Int8Array -128 ~ 127 1 8 位二进制有符号整数(第一位表示符号)
Uint8Array 0 ~ 255 1 8 位二进制无符号整数
Int16Array -32768 ~ 32767 2 16 位二进制有符号整数
Uint16Array 0 ~ 65535 2 16 位二进制无符号整数
  1. // 每个字节 8 位
  2. const buffer = new ArrayBuffer(8)
  3. console.log(buffer.byteLength); // 8 个字节,总共 64 位
  4. const int8Array = new Int8Array(buffer); // 每个元素占用 1 个字节
  5. console.log(int8Array.length); // 8 个 8 位元素,总共 64 位
  6. const int16Array = new Int16Array(buffer); // 每个元素占用 2 个字节
  7. console.log(int16Array.length); // 4 个 16 位元素,总共 64 位

DataView

DataView 视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口

  • setInt8 从 DataView 起始位置以 byte 为计数的指定偏移(byteOffset)处存储 8 bit 数(一个字节)
  • getInt8 从 DataView 起始位置以 byte 为计数的指定偏移(byteOffset)处获取 8 bit 数(一个字节)
  1. // 语法
  2. new DataView(buffer [, byteOffset [, byteLength]])
  1. const buffer = new ArrayBuffer(8)
  2. console.log(buffer.byteLength);
  3. const dataView = new DataView(buffer);
  4. // 设置第 1 个字节位置值为 1
  5. dataView.setInt8(0, 1) // byteOffset value
  6. // 设置第 2 个字节位置值为 2
  7. dataView.setInt8(1, 2)
  8. // 从字节偏移 0 位置开始读取 16 位元素
  9. dataView.getInt16(0); // 258

截屏2022-01-29 下午5.07.42.png

Blob

Blob 表示一个不可变的、原始数据的类文件对象。Blob 表示的不一定是 JavaScript 原生格式的数据。

File 继承自 Blob,并将其扩展使其支持用户系统上的文件。

  • 使用 Blob 构造函数将非 blob 数据转换为 blob
  • 创建 Blob 的子集,只需要调用 Blob 实例的 slice 方法即可
  1. new Blob(blobParts[, options])
  2. // blobParts 可以是 String、TypedArray、ArrayBuffer 数组
  3. // options
  4. // type: blob 中内容的 MIME 类型
  5. // endings: 指定行结束符 \n 如何写入,默认 transparent,表示不变,native 表示更改为宿主环境的换行符

属性:size、type
常用方法:

  • slice:分割,有兼容问题
  • text:返回一个 promise UTF-8 格式
  • arrayBuffer:返回一个 promise

FileReader

FileReader 可以读取文件内容,使用 File 或者 Blob 指定读取的文件或者数据,并转换为对应的格式

封装通用的读取方法:

  1. function readBlob(blob, type) {
  2. return new Promise((resolve) => {
  3. const reader = new FileReader();
  4. switch(type) {
  5. case "ArrayBuffer":
  6. reader.readAsArrayBuffer(blob)
  7. break;
  8. case "DataURL": // base64 字符串
  9. reader.readAsDataURL(blob)
  10. break;
  11. case "Text":
  12. reader.readAsText(blob, 'utf-8')
  13. break;
  14. }
  15. reader.onload = (e) => {
  16. resolve(e.target.result);
  17. }
  18. })
  19. }

Object URL

可以使用浏览器新的 API URL 对象通过方法生成一个地址来表示 Blob 数据,格式为 blob:<origin>/<uuid>

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

URL.revokeObjectURL 静态方法用来释放一个之前已经存在的、通过 createObjectURL 创建的 URL 对象

用于文件下载:

  1. function download(blob, filename) {
  2. // 创建 objectURL
  3. const objectURL = URL.createObjectURL(blob);
  4. // 创建 a 标签,并手动点击
  5. const a = document.createElement("a");
  6. a.download = filename;
  7. a.href = objectURL;
  8. a.rel = 'noopener';
  9. a.dispatchEvent(new MouseEvent("click"));
  10. // 释放 objectURL
  11. URL.revkoeObjectURL(objectURL);
  12. }

实践

图片预览和裁剪上传

读取上传图片并预览

  1. const Image = () => {
  2. const [file, setFile] = useState(null);
  3. const [dataURL, setDataURL] = useState("");
  4. const imageRef = useRef(null);
  5. const handleChange = (e) => {
  6. const file = e.target.files[0];
  7. const fileReader = new FileReader();
  8. fileReader.onload = (e) => {
  9. setFile(file);
  10. setDataURL(e.target.result)
  11. }
  12. fileReader.readAsDataURL(file);
  13. }
  14. return (
  15. <div>
  16. <div>
  17. <input
  18. type="file"
  19. accept="image/*"
  20. onChange={handleChange}
  21. />
  22. {dataURL && <image ref={imageRef} src={dataURL} />}
  23. </div>
  24. </div>
  25. )
  26. }

图片拖拽与裁剪

  1. const Image = () => {
  2. //...
  3. const imageRef = useRef(null);
  4. const [canvasRef, drawImage] = useDrawImage(imageRef);
  5. // 放大缩小逻辑
  6. const {bigger, smaller, reset} = useScale(drawImage);
  7. const resetImage = () => {
  8. reset()
  9. }
  10. useEffect(() => {
  11. // 图片更换逻辑
  12. this.imageRef.current?.onload = () => resetImage();
  13. }, [dataURL])
  14. const handleMouseDown = () => {}
  15. const handleMouseMove = () => {
  16. drawImage()
  17. }
  18. const handleMouseUp = () => {}
  19. return (
  20. <div>
  21. <div
  22. onMouseDown={handleMouseDown}
  23. onMouseUp={handleMouseUp}
  24. onMouseMove={handleMouseMove}
  25. >
  26. <canvas ref={canvasRef} width="300px" height="300px" />
  27. </div>
  28. </div>
  29. )
  30. }

图片绘制逻辑:

  1. const useDrawImage = (imageRef) => {
  2. const canvasRef = useRef(null);
  3. const drawImage = (scale) => {
  4. const image = imageRef.current;
  5. if (!image) return;
  6. const canvas = canvas.current;
  7. const imageWidth = image?.width;
  8. const imageHeight = image?.height;
  9. const ctx = canvas.getContext("2d");
  10. ctx.clearRect(0, 0, canvas.width, canvas.height);
  11. // 初始图片完整呈现
  12. if (imageWidth > imageHeight) {
  13. const ratio = canvas.width / imageWidth;
  14. imageWidth = canvas.width;
  15. imageHeight = imageHeight * ratio;
  16. } else {
  17. const ratio = canvas.height / imageHeight;
  18. imageHeight = canvas.height;
  19. imageWidth = imageWidth * ratio;
  20. }
  21. // 放大或缩小
  22. imageWidth *= scale
  23. imageHeight *= scale
  24. // 绘制
  25. ctx.drawImage(
  26. image,
  27. (canvas.width - imageWidth)/2,
  28. (canvas.height - imageHeight)/2,
  29. imageWidth,
  30. imageHeight
  31. );
  32. }
  33. return [canvasRef, drawImage]
  34. }

放大缩小逻辑:

  1. const useScale = (callback) => {
  2. // 放大缩小逻辑
  3. const [scale, setScale] = useState(1);
  4. const bigger = () => setScale(scale => scale + 0.1);
  5. const smaller = () => setScale(scale => scale - 0.1);
  6. const reset = () => setScale(1)
  7. useEffect(() => {
  8. callback(scale);
  9. }, [scale])
  10. return {
  11. scale,
  12. bigger,
  13. smaller,
  14. reset
  15. }
  16. }

音频的裁剪和预览