# 从图片剪裁聊前端文件上传相关的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位整数)数组视图,Int16Array
16位整数数组视图等。数组成员都是相同的数据类型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
内容的ReadableStream
Blob.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/png
encoderOptions
可选:在指定图片格式为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