
基础对象
ArrayBuffer
ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,它是一个字节数组,在其他语言中通常称为 byte array。
ArrayBuffer 不能直接操作,而是通过 TypedArray 或 DataView 来操作,它们在缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
// 创建一个 8 个字节长度的 buffer,ArrayBuffer 的初始化必须固定长度const buffer = new ArrayBuffer(8)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 位二进制无符号整数 |
// 每个字节 8 位const buffer = new ArrayBuffer(8)console.log(buffer.byteLength); // 8 个字节,总共 64 位const int8Array = new Int8Array(buffer); // 每个元素占用 1 个字节console.log(int8Array.length); // 8 个 8 位元素,总共 64 位const int16Array = new Int16Array(buffer); // 每个元素占用 2 个字节console.log(int16Array.length); // 4 个 16 位元素,总共 64 位
DataView
DataView 视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口
setInt8从 DataView 起始位置以 byte 为计数的指定偏移(byteOffset)处存储 8 bit 数(一个字节)getInt8从 DataView 起始位置以 byte 为计数的指定偏移(byteOffset)处获取 8 bit 数(一个字节)
// 语法new DataView(buffer [, byteOffset [, byteLength]])
const buffer = new ArrayBuffer(8)console.log(buffer.byteLength);const dataView = new DataView(buffer);// 设置第 1 个字节位置值为 1dataView.setInt8(0, 1) // byteOffset value// 设置第 2 个字节位置值为 2dataView.setInt8(1, 2)// 从字节偏移 0 位置开始读取 16 位元素dataView.getInt16(0); // 258

Blob
Blob 表示一个不可变的、原始数据的类文件对象。Blob 表示的不一定是 JavaScript 原生格式的数据。
File 继承自 Blob,并将其扩展使其支持用户系统上的文件。
- 使用 Blob 构造函数将非 blob 数据转换为 blob
- 创建 Blob 的子集,只需要调用 Blob 实例的 slice 方法即可
new Blob(blobParts[, options])// blobParts 可以是 String、TypedArray、ArrayBuffer 数组// options// type: blob 中内容的 MIME 类型// endings: 指定行结束符 \n 如何写入,默认 transparent,表示不变,native 表示更改为宿主环境的换行符
属性:size、type
常用方法:
- slice:分割,有兼容问题
- text:返回一个 promise
UTF-8 格式 - arrayBuffer:返回一个 promise
FileReader
FileReader 可以读取文件内容,使用 File 或者 Blob 指定读取的文件或者数据,并转换为对应的格式
封装通用的读取方法:
function readBlob(blob, type) {return new Promise((resolve) => {const reader = new FileReader();switch(type) {case "ArrayBuffer":reader.readAsArrayBuffer(blob)break;case "DataURL": // base64 字符串reader.readAsDataURL(blob)break;case "Text":reader.readAsText(blob, 'utf-8')break;}reader.onload = (e) => {resolve(e.target.result);}})}
Object URL
可以使用浏览器新的 API URL 对象通过方法生成一个地址来表示 Blob 数据,格式为 blob:<origin>/<uuid>
URL.createObjectURL 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL,这个 URL 的生命周期和创建它的窗口中的 document 绑定。参数为指定的 File 或者 Blob 对象。
URL.revokeObjectURL 静态方法用来释放一个之前已经存在的、通过 createObjectURL 创建的 URL 对象
用于文件下载:
function download(blob, filename) {// 创建 objectURLconst objectURL = URL.createObjectURL(blob);// 创建 a 标签,并手动点击const a = document.createElement("a");a.download = filename;a.href = objectURL;a.rel = 'noopener';a.dispatchEvent(new MouseEvent("click"));// 释放 objectURLURL.revkoeObjectURL(objectURL);}
实践
图片预览和裁剪上传
读取上传图片并预览
const Image = () => {const [file, setFile] = useState(null);const [dataURL, setDataURL] = useState("");const imageRef = useRef(null);const handleChange = (e) => {const file = e.target.files[0];const fileReader = new FileReader();fileReader.onload = (e) => {setFile(file);setDataURL(e.target.result)}fileReader.readAsDataURL(file);}return (<div><div><inputtype="file"accept="image/*"onChange={handleChange}/>{dataURL && <image ref={imageRef} src={dataURL} />}</div></div>)}
图片拖拽与裁剪
const Image = () => {//...const imageRef = useRef(null);const [canvasRef, drawImage] = useDrawImage(imageRef);// 放大缩小逻辑const {bigger, smaller, reset} = useScale(drawImage);const resetImage = () => {reset()}useEffect(() => {// 图片更换逻辑this.imageRef.current?.onload = () => resetImage();}, [dataURL])const handleMouseDown = () => {}const handleMouseMove = () => {drawImage()}const handleMouseUp = () => {}return (<div><divonMouseDown={handleMouseDown}onMouseUp={handleMouseUp}onMouseMove={handleMouseMove}><canvas ref={canvasRef} width="300px" height="300px" /></div></div>)}
图片绘制逻辑:
const useDrawImage = (imageRef) => {const canvasRef = useRef(null);const drawImage = (scale) => {const image = imageRef.current;if (!image) return;const canvas = canvas.current;const imageWidth = image?.width;const imageHeight = image?.height;const ctx = canvas.getContext("2d");ctx.clearRect(0, 0, canvas.width, canvas.height);// 初始图片完整呈现if (imageWidth > imageHeight) {const ratio = canvas.width / imageWidth;imageWidth = canvas.width;imageHeight = imageHeight * ratio;} else {const ratio = canvas.height / imageHeight;imageHeight = canvas.height;imageWidth = imageWidth * ratio;}// 放大或缩小imageWidth *= scaleimageHeight *= scale// 绘制ctx.drawImage(image,(canvas.width - imageWidth)/2,(canvas.height - imageHeight)/2,imageWidth,imageHeight);}return [canvasRef, drawImage]}
放大缩小逻辑:
const useScale = (callback) => {// 放大缩小逻辑const [scale, setScale] = useState(1);const bigger = () => setScale(scale => scale + 0.1);const smaller = () => setScale(scale => scale - 0.1);const reset = () => setScale(1)useEffect(() => {callback(scale);}, [scale])return {scale,bigger,smaller,reset}}
