# 从图片剪裁聊前端文件上传相关的API
# FileReader
FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File或Blob对象指定要读取的文件或数据。
图片剪裁首先要上传图片,这个时候可以利用html5的<input type="file">来上传图片,然后利用FileReader来实现异步读取文件机制。这个时候File对象可以是来自用户在一个<input>元素上选择文件后返回的FileList对象
<input type="file" name="avatar" id="file" @change="selectAvatar" :accept="acceptType">
selectAvatar(e) {
const file = e.target.files[0]
let reader = new FileReader()
reader.onload = (e) => {
console.log(e)
let data
if(e.target.result) {
data = e.target.result
}
}
reader.readAsDataURL(file)
}
2
3
4
5
6
7
8
9
10
11
12

通过
FileReader我们可以将图片文件转化成DataURL,就是以data:image/png;base64开头的一种URL,然后可以直接放在image.src里,这样本地图片就显示出来了。
# 方法
| 方法名 | 描述 |
|---|---|
abort | 中止读取操作 |
readAsArrayBuffer | 异步按字节读取文件内容,结果用ArrayBuffer对象表示 |
readAsBinaryString | 异步按字节读取文件内容,结果用文件的原始二进制串表示 |
readAsDataURL | 异步读取文件内容,结果用data:URL的字符串形式表示 |
readAsText | 异步按字符读取文件内容,结果用字符串形式表示 |
# 事件
| 事件名 | 描述 |
|---|---|
onabort | 中断时触发 |
onerror | 出错时触发 |
onload | 文件读取成功完成时触发 |
onloadend | 读取完成触发(无论成功或失败) |
onloadstart | 读取开始时触发 |
onprogress | 读取中 |
# 二进制数组
先来介绍
ArrayBuffer,是因为FileReader有个readAsArrayBuffer()的方法,如果被读的文件是二进制数据,那用这个方法去读应该是最合适的,读出来的数据,就是一个Arraybuffer对象
ArrayBuffer对象,TypedArray视图,DataView视图是JavaScript操作二进制数据的一个接口。它们都以数组的语法处理二进制数据,所以统称二进制数组。
ArrayBuffer对象:代表内存中的一段二进制数据,可以通过视图进行操作。可以用数组的方法操作内存。TypedArray视图:有9种类型的视图,如Uint8Array(无符号8位整数)数组视图,Int16Array16位整数数组视图等。数组成员都是相同的数据类型DataView视图:可以自定义复合格式的视图,每个字节的类型都可以自定义的。意思就是数组成员可以是不同的数据类型。
一言以蔽之,ArrayBuffer对象代表原始的二进制数据,TypedArray视图用于读/写简单类型的二进制数据,DataView视图用于读/写复杂类型的二进制数据。二进制数组不是真正的数据,而是类数组
# ArrayBuffer对象
代表存储二进制数据的一段内存,不能直接读/写,只能通过另外两个视图对象读/写,视图的作用是以指定格式解读二进制数据。
var buf = new ArrayBuffer(32)
console.log(buf.byteLength) // ArrayBuffer 对象有实例属性 byteLength ,表示当前实例占用的内存字节长度(单位字节)
2
3
上面代码生成一段32字节的内存区域,每个字节的值默认都是0。该对象的参数是所需要的内存大小(单位字节)
TIP
ArrayBuffer.prototype.slice()该方法允许将内存区域的一部分复制生成一个新的ArrayBuffer对象。
var buffer = new ArrayBuffer(8)
var newBuffer = buffer.slice(0,3)
2
上面代码复制了buffer对象的前3个字节,生成newBuffer一个新的ArrayBuffer对象。该方法其实包含了分配一段新内存,将原来的ArrayBuffer对象复制过去两步骤。该方法如果省略了第二个参数,则默认到原始内存对象的结尾。
ArrayBuffer.isView()返回一个布尔值,表示参数是否为ArrayBuffer的视图实例。
var buffer = new ArrayBuffer(8)
ArrayBuffer.isView(buffer) // false
var v = new Int32Array(buffer)
ArrayBuffer.isView(v) // true
2
3
4
5
ArrayBuffer对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做视图。
# DataView视图
为了读写这段内容,需要为它指定视图。DataView视图的创建,需要提供ArrayBuffer对象实例作为参数。
var dataView = new DataView(buf)
dataView.getUint8(0) // 0 因为原始内存ArrayBuffer对象默认所有位都是0
2
# TypedArray视图
TypedArray视图,与DataView视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。
var buffer = new ArrayBuffer(12)
var x1 = new Int32Array(buffer) // 32位带符号整数
x1[0] = 1;
var x2 = new Uint8Array(buffer) // 8位不带符号整数
x2[0] = 2
x1[0] // 2
2
3
4
5
6
7
8
9
由于两个视图对应同一段内存,因此一个视图修改底层内存会影响到另一个视图。TypedArray视图还可以接收普通数组作为参数,直接分配内存生成底层的ArrayBuffer实例,同时完成这段内存的赋值。
const imageData = this.getDataURL()
const b64 = imageData.replace('data:image/png;base64,', '')
const binary = atob(b64) // 如图乱码所以需要下一步取编码
const array = []
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i))
}
let newBlob = new Blob([new Uint8Array(array)], {type: 'image/png'})
2
3
4
5
6
7
8
上面的自己封装的头像剪裁组件例子中array就是普通数组,新建了一个不带符号的8位整数视图,对底层内存的赋值也同时完成。使用Uint8Array是因为b64字符串在atob解码后一一获得具体字符的Unicode编码的值范围在0-255

- 普通数组和
TypedArray数组差异:- 后者数组成员连续,不会有空位
- 后者数组成员默认值为0
- 后者数组所有成员都是同一种类型 除此之外,所有在数组上使用的方法都能在其上使用。
var v3 = new Int16Array(b,2,2)// 创建一个指向b的Int16视图,开始于字节2,长度为2
视图的构造函数可以接受3个参数:
- 第一个参数(必选):视图对应的底层
ArrayBuffer对象。 - 第二个参数(可选):视图开始的字节序号,默认从0开始。必须与所建立的数据类型一致,否则报错
- 第三个参数(可选):视图包含的数据个数,默认直到本段内存区域结束。
var buffer = new ArrayBuffer(8)
var i16 = new Int16Array(buffer, 1)
// Uncaught RangeError: start offset of Int16Array
// should be a multiple of 2
2
3
4
上面例子新生成一个8个字节的原始内存对象,然后在这个对象的第一个字节建立带符号的16位整数视图,结果报错。因为带符号的16位整数需要2个字节,所以这就是数据类型不一致引起的报错。
# Blob对象
Blob是用来支持文件操作的。简单的说:在JS中,有两个构造函数 File 和 Blob, 而File继承了所有Blob的属性。
Blob具体实现的功能:

要从其他非
blob对象和数据构造一个Blob,请使用Blob()构造函数。
Blob(blobParts[,options])
返回一个新创建的Blob对象,其内容由参数中给定定数组串联组成。
- 属性
Blob.size:只读,Blob对象中包含数据定大小(字节)Blob.type:只读,一个字符串,表示该Blob对象所包含数据的MIME类型。如果类型未知,则为空字符串。
let newBlob = new Blob([new Uint8Array(array)], {type: 'image/png'})
console.log(newBlob)
2

- 方法
Blob.slice([start[,end[,contentType]]]):返回一个新的Blob对象,包含了源Blob对象中指定范围内的数据。用于文件分片上传Blob.stream():返回一个能读取blob内容的ReadableStreamBlob.text():返回一个promise且包含blob所有内容的UTF-8格式的USVString。Blob.arrayBuffer():返回一个promise且包含blob所有内容的二进制格式的ArrayBuffer
# base64

其实就是Data URI,前缀为data:协议的URL,其允许内容创建者向文档嵌入小文件。Data URI 由四个部分组成:前缀(data:)、指示数据类型的MIME类型、如果非文本则为可选的base64标记、数据本身:
data:[<mediatype>][;base64],<data>
TIP
如果数据是文本类型,你可以直接将文本嵌入 (根据文档类型,使用合适的实体字符或转义字符)。如果是二进制数据,你可以将数据进行base64编码之后再进行嵌入。
data:,Hello%2C%20World! # 简单的 text/plain 类型数据
data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D # 上一条示例的 base64 编码版本
2
3
在 JavaScript 中,有两个函数被分别用来处理解码和编码base64字符串:atob()和btoa()
atob()函数能够解码通过base-64编码的字符串数据。相反地,btoa()函数能够从二进制数据“字符串”创建一个base-64编码的ASCII字符串。
然后取出其中base64信息,再用window.atob转换成由二进制字符串。但window.atob转换后的结果仍然是字符串,直接给Blob还是会出错。所以又要用Uint8Array转换一下。
TIP
如果不仔细看,真的会误把data URI看成data URL,然后用URL的方式去理解URI,其实不然!URL是uniform resource locator的缩写,在web中的每一个可访问资源都有一个URL地址,例如图片,HTML文件,js文件以及style sheet文件,我们可以通过这个地址去download这个资源。其实URL是URI的子集,URI是uniform resource identifier的缩写。URI是用于获取资源,包括其附加的信息的一种协议。附加信息可能是地址,也可能不是地址,如果是地址,那么这时URI就变成URL了。注意的是data URI不是URL,因为它并不包含资源的公共地址。
// base64转换成buffer
base64ToBuffer(base64Url) {
let parts = base64Url.split(';base64,');
// let contentType = parts[0].split(':')[1];
let raw = window.atob(parts[1]);
let rawLength = raw.length;
let uInt8Array = new Uint8Array(rawLength);
for(let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return uInt8Array
}
2
3
4
5
6
7
8
9
10
11
12
# canvas
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
context.drawImage(this.img, 0,0, sw, sh, dx, dy, dw, dh) // sw/sh 被剪裁图像的宽高。 dx/dy 在画布上放置的位置。 width 要使用的图像宽度
const imageData = canvas.toDataURL()
2
3
4
toDataURL方法返回一个包含图片展示的data URI
canvas.toDataURL(type, encoderOptions);
参数说明:
type可选:图片格式,默认为image/pngencoderOptions可选:在指定图片格式为image/jpeg或image/webp的情况下,可以从0到1的区间内选择图片的质量。如果超出取值范围,将会使用默认值0.92。其他参数会被忽略。
需要注意的点:
- 如果画布的高度或宽度是
0,那么会返回字符串“data:,”。 - 如果传入的类型非
“image/png”,但是返回的值以“data:image/png”开头,那么该传入的类型是不支持的。 - Chrome支持
“image/webp”类型。
- 如果画布的高度或宽度是
# FormData
提交请求有两种方式:一种是
Ajax,另外一种就是通过表单提交 通过File API能够访问到文件内容,利用这一点就可以通过XHR直接把文件上传到服务器。当然啦,把文件内容放到send()方法中,再通过POST请求,的确很容易就能实现上传。但这样做传递的是文件内容,因而服务器必须收集提交的内容,然后再把它们保存到另一个文件中。其实更好的做法是以表单提交的方式来上传文件。
这时候裁剪后的文件就储存在blob里了,我们可以把它当作是普通文件一样,加入到FormData里,并上传至服务器了。
let fd = new FormData()
fd.append('file',file)
xhr.send(fd)
2
3
4