基础对象
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 个字节位置值为 1
dataView.setInt8(0, 1) // byteOffset value
// 设置第 2 个字节位置值为 2
dataView.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) {
// 创建 objectURL
const objectURL = URL.createObjectURL(blob);
// 创建 a 标签,并手动点击
const a = document.createElement("a");
a.download = filename;
a.href = objectURL;
a.rel = 'noopener';
a.dispatchEvent(new MouseEvent("click"));
// 释放 objectURL
URL.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>
<input
type="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>
<div
onMouseDown={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 *= scale
imageHeight *= 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
}
}