# DrawImage4js
**Repository Path**: iosrev/DrawImage4js
## Basic Information
- **Project Name**: DrawImage4js
- **Description**: No description available
- **Primary Language**: JavaScript
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-06-30
- **Last Updated**: 2021-06-30
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 写在最前面
### 最开始的一个小需求
前两天项目中有个小需求:前端下载后台小哥返回的二进制流文件。
起初接到这个需求时,我感觉这很简单啊(虽然我不会,但可以百度啊,,,,)

然后就写出了如下的代码:
```js
let blob = new Blob([res.data]);
let fileName = `Cosen.csv`;
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, fileName);
} else {
let link = document.createElement("a");
let evt = document.createEvent("HTMLEvents");
evt.initEvent("click", false, false);
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.style.display = "none";
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(link.href);
}
```
这一段代码,我大概~~强行~~解释一下:
首先判断`window.navigator.msSaveOrOpenBlob`是为了兼容`IE`(谁要兼容这 xx`IE`!!)
然后非`IE`的通过`URL.createObjectURL()`将`Blob`(`Blob`是啥?不知道?没关系,我下面会具体~~装逼~~讲解的)构建为一个`object URL`对象、指定文件名&文件类型、创建`a`链接模拟点击实现下载,最后通过`URL.revokeObjectURL`释放创建的对象。
功能虽然实现了,但其实我是似懂非懂的~

### 紧接着 一个不那么简单的需求
没过几天,产品又给我提了一个需求:图片裁剪上传及预览。
虽然听过类似的需求,但自己手写还真的没写过,然后我就开始了网上冲浪时光(各种搜索,,,)。但这次,没有想象中那么简单了~~
网上看到的都是诸如`FileReader`、`canvas`、`ArrayBuffer`、`FormData`、`Blob`这些名词。我彻底懵了,这些平时都只是听过啊,用的也不多啊。经过了一番学习,我发现这些都属于前端二进制的知识范畴,所以在搞业务前,我准备先把涉及到的前端二进制梳理一遍,正所谓:底层基础决定上层建筑嘛 🙈

## `FileReader`
`HTML5`定义了`FileReader`作为文件`API`的重要成员用于读取文件,根据`W3C`的定义,`FileReader`接口提供了读取文件的方法和包含读取结果的事件模型。
### 创建实例
```js
var reader = new FileReader();
```
### 方法
| 方法名 | 描述 |
| -------------------- | ----------------------------------------------------- |
| `abort` | 中止读取操作 |
| `readAsArrayBuffer` | 异步按字节读取文件内容,结果用 `ArrayBuffer` 对象表示 |
| `readAsBinaryString` | 异步按字节读取文件内容,结果为文件的二进制串 |
| `readAsDataURL` | 异步读取文件内容,结果用 `data:url` 的字符串形式表示 |
| `readAsText` | 异步按字符读取文件内容,结果用字符串形式表示 |
### 事件
| 事件名 | 描述 |
| ------------- | ------------------------------ |
| `onabort` | 中断时触发 |
| `onerror` | 出错时触发 |
| `onload` | 文件读取成功完成时触发 |
| `onloadend` | 读取完成触发(无论成功或失败) |
| `onloadstart` | 读取开始时触发 |
| `onprogress` | 读取中 |
### 示例
下面我们尝试把一个文件的内容通过字符串的方式读取出来:
```js
;
document.getElementById("upload").addEventListener(
"change",
function (e) {
var file = this.files[0];
const reader = new FileReader();
reader.onload = function () {
const result = reader.result;
console.log(result);
};
reader.readAsText(file);
},
false
);
```
## `ArrayBuffer`/`TypedArray`/`DataView 对象`
### `ArrayBuffer`
先来看下`ArrayBuffer`的功能:
先来介绍`ArrayBuffer` ,是因为 `FileReader` 有个 `readAsArrayBuffer()`的方法,如果被读的文件是二进制数据,那用这个方法去读应该是最合适的,读出来的数据,就是一个 `Arraybuffer` 对象,来看下定义:
> `ArrayBuffer` 对象用来表示通用的、固定长度的原始二进制数据缓冲区.`ArrayBuffer` 不能直接操作,而是要通过类型数组对象或 `DataView` 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容.
`ArrayBuffer`也是一个构造函数,可以分配一段可以存放数据的连续内存区域。
```js
const buffer = new ArrayBuffer(8);
// ArrayBuffer 对象有实例属性 byteLength ,表示当前实例占用的内存字节长度(单位字节)
console.log(buffer.byteLength);
```
由于无法对 `Arraybuffer` 直接进行操作,所以我们需要借助其他对象来操作. 所有就有了 `TypedArray`(类型数组对象)和 `DataView`对象。
### `DataView 对象`
上面代码生成了一段 8 字节的内存区域,每个字节的值默认都是 0。
为了读写这段内容,需要为它指定视图。`DataView`视图的创建,需要提供`ArrayBuffer`对象实例作为参数。
`DataView`视图是一个可以从二进制`ArrayBuffer`对象中读写多种数值类型的底层接口。
- `setint8()` 从`DataView`起始位置以`byte`为计数的指定偏移量(`byteOffset`)处存储一个`8-bit`数(一个字节)
- `getint8()` 从`DataView`起始位置以`byte`为计数的指定偏移量(`byteOffset`)处获取一个`8-bit`数(一个字节)
#### 调用
```js
new DataView(buffer, [, byteOffset [, byteLength]])
```
#### 示例
```js
let buffer = new ArrayBuffer(2);
console.log(buffer.byteLength); // 2
let dataView = new DataView(buffer);
dataView.setInt(0, 1);
dataView.setInt(1, 2);
console.log(dataView.getInt8(0)); // 1
console.log(dataView.getInt8(1)); // 2
console.log(dataView.getInt16(0)); // 258
```

### `TypedArray`
另一种`TypedArray`视图,与`DataView`视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。
`TypedArray`对象描述了一个底层的二进制数据缓存区(`binary data buffer`)的一个类数组视图(`view`)。
但它本身不可以被实例化,甚至无法访问,你可以把它理解为接口,它有很多的实现。
#### 实现方法
| 类型 | 单个元素值的范围 | 大小(bytes) | 描述 |
| ----------- | ---------------- | ------------- | --------------------- |
| Int8Array | -128 to 127 | 1 | 8 位二进制有符号整数 |
| Uint8Array | 0 to 255 | 1 | 8 位无符号整数 |
| Int16Array | -32768 to 32767 | 2 | 16 位二进制有符号整数 |
| Uint16Array | 0 to 65535 | 2 | 16 位无符号整数 |
#### 示例
```js
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength); // 8
const int8Array = new Int8Array(buffer);
console.log(int8Array.length); // 8
const int16Array = new Int16Array(buffer);
console.log(int16Array.length); // 4
```
## `Blob`
`Blob`是用来支持文件操作的。简单的说:在`JS`中,有两个构造函数 `File` 和 `Blob`, 而`File`继承了所有`Blob`的属性。
所以在我们看来,`File`对象可以看作一种特殊的`Blob`对象。
上面说了,`File`对象是一种特殊的`Blob`对象,那么它自然就可以直接调用`Blob`对象的方法。让我们看一看`Blob`具体有哪些方法,以及能够用它们实现哪些功能:
是的,我们这里更加倾向于实战中的应用~

关于`Blob`的更具体介绍可以参考[Blob](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob)
## `atob` 和 `btoa`
`base64` 相信大家都不会陌生吧(不知道的看[这里](https://www.baidu.com/)),最常用的操作可能就是图片转 `base64` 了吧?
在之前要在字符串跟`base64`之间互转,我们可能需要去网上拷一个别人的方法,而且大部分情况下,你没有时间去验证这个方法是不是真的可靠,有没有`bug`。
从`IE10+`浏览器开始,所有浏览器就原生提供了`Base64`编码解码方法。
### Base64 解码
```js
var decodedData = window.atob(encodedData);
```
### Base64 编码
```js
var encodedData = window.btoa(stringToEncode);
```
## `Canvas`中的`ImageData`对象
关于`Canvas`,这里我就不做过多介绍了,具体可参考[canvas 文档](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API)
今天主要说一下`Canvas`中的`ImageData`对象(也是为下面的那个图片裁剪的项目做一些基础知识的铺垫~)

`ImageData`对象中存储着`canvas`对象真实的像素数据,它包含以下几个只读属性:
- `width`:图片宽度,单位是像素
- `height`:图片高度,单位是像素
- `data`:`Uint8ClampedArray`类型的一维数组,包含着`RGBA`格式的整型数据,范围在 0 至 255 之间(包括 255)。
### 创建一个`ImageData`对象
使用`createImageData()` 方法去创建一个新的,空白的`ImageData`对象。
```js
var myImageData = ctx.createImageData(width, height);
```
上面代码创建了一个新的具体特定尺寸的`ImageData`对象。所有像素被预设为透明黑。
### 得到场景像素数据
为了获得一个包含画布场景像素数据的`ImageData`对象,你可以用`getImageData()`方法:
```js
var myImageData = ctx.getImageData(left, top, width, height);
```
### 在场景中写入像素数据
你可以用`putImageData()`方法去对场景进行像素数据的写入。
```js
ctx.putImageData(myImageData, dx, dy);
```
### `toDataURL` 将`canvas`转为 `data URI`格式
有如下`