前端与用户的文件交互需要由用户触发,用户可以通过三种方式触发文件上传。
原文地址:https://juejin.im/post/5a193b4bf265da43052e528a
用户可通过以下三种方式操作触发:
- 通过 input type=”file” 选择本地文件
- 通过拖拽的方式把文件拖过来
- 在编辑框里面复制粘贴
通过 input type=”file” 选择本地文件
FormData
通常还会自定义一个按钮,然后盖在它上面,因为 type=”file”的 input 不好改变样式
1
2
3
<form>
<input type="file" id="file-input" name="fileContent" />
</form>
1
2
3
4
5
6
$("#file-input").on("change", function () {
console.log(`file name is ${this.value}`);
let formData = new FormData(this.form);
formData.append("fileName", this.value);
console.log(formData);
});
结果:
- 文件的路径是一个假的路径,也就是说在浏览器无法获取到文件的真实存放位置
- FormData 打印出来是一个空的 Objet,但并不是说它的内容是空的,只是它对前端开发人员是透明的,无法查看、修改、删除里面的内容,只能 append 添加字段
FileReader
FormData 无法得到文件的内容,而使用 FileReader 可以读取整个文件的内容。用户选择文件之后,input.files 就可以得到用户选中的文件
1
<input type="file" id="uploadBtn"></input>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const uploadBtn = document.getElementById("uploadBtn");
uploadBtn.onchange = (e) => {
readFile(e.srcElement.files[0]);
};
function readFile(file) {
const reader = new FileReader();
const fileType = file.type;
// 按 base64 的方式读取
// reader.readAsDataURL(file);
// 以原始二进制方式读取,读取结果可直接转成整数数组
// reader.readAsArrayBuffer(file);
reader.readAsDataURL(file);
reader.onload = function (evt) {
if (/^image\/[jpeg|png|gif]/.test(fileType)) {
console.log(evt.target.result);
}
};
}
通过拖拽的方式把文件拖过来
设置一个可接受区域
1
<div class="img-container">drop your image here</div>
然后监听它的拖拽事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$(".img-container")
.on("dragover", function (event) {
event.preventDefault();
})
.on("drop", function (event) {
event.preventDefault();
// 数据在event的dataTransfer对象里
let file = event.originalEvent.dataTransfer.files[0];
// 然后就可以使用FileReader进行操作
FileReader.readAsDataURL(file);
// 或者是添加到一个FormData
let formData = new FormData();
formData.append("fileContent", file);
});
在编辑框里面复制粘贴
通常是在一个编辑框里操作,如把 div 的 contenteditable
设置为 true
1
<div contenteditable="true">hello, paste your image here</div>
粘贴的数据是在 event.clipboardData.files
1
2
3
$("#editor").on("paste", function (event) {
let file = event.originalEvent.clipboardData.files[0];
});
但是 Safari 的粘贴不是通过 event 传递的,它是直接在输入框里面添加一张图片:
它新建了一个 img 标签,并把 img 的 src 指向一个 blob 的本地数据。blob 是一种类文件的存储格式,它可以存储几乎任何格式的内容,如 json:
1
2
let data = { hello: "world" };
let blob = new Blob([JSON.stringify(data)], { type: "application/json" });
为了获取本地的 blob 数据,我们可以用 ajax 发个本地的请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$("#editor").on("paste", function (event) {
// 需要setTimeout 0等图片出来了再处理
setTimeout(() => {
let img = $(this).find("img[src^='blob']")[0];
console.log(img.src);
// 用一个xhr获取blob数据
let xhr = new XMLHttpRequest();
xhr.open("GET", img.src);
// 改变mime类型
xhr.responseType = "blob";
xhr.onload = function () {
// response就是一个Blob对象
console.log(this.response);
};
xhr.send();
}, 0);
});
blob 打印出来是这样的
和 File 一样,可以使用 FileReader 读取它的内容
1
2
3
4
5
6
7
8
9
10
function readBlob(blobImg) {
let fileReader = new FileReader();
fileReader.onload = function () {
console.log(this.result);
};
fileReader.onerror = function (err) {
console.log(err);
};
fileReader.readAsDataURL(blobImg);
}
还能使用 window.URL 读取,这是一个新的 API,经常和 Service Worker 配套使用,因为 SW 里面常常要解析 url
1
2
3
4
5
6
7
8
function readBlob(blobImg) {
let urlCreator = window.URL || window.webkitURL;
// 得到base64结果
let imageUrl = urlCreator.createObjectURL(blobImg);
return imageUrl;
}
readBlob(this.response);
发送数据
我们使用了三种方式获取文件内容,最后得到:
- FormData 格式
- FileReader 读取得到的 base64 或者 ArrayBuffer 二进制格式
FormData 格式
如果直接就是一个 FormData 了,那么直接用 ajax 发出去就行了,不用做任何处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
let form = document.querySelector("form"),
formData = new FormData(form);
formData.append("fileName", "photo.png");
$.ajax({
url: "/upload",
type: "POST",
data: formData,
// 因为jQuery会自动把内容做一些转义,并且根据data自动设置请求mime类型,这里告诉jQuery直接用xhr.send发出去就行了。
processData: false, // 不处理数据
contentType: false, // 不设置内容类型
});
base64
使用比较多的应该是 base64,因为前端经常要处理图片,读取为 base64 之后就可以把它画到一个 canvas 里面,然后就可以做一些处理,如压缩、裁剪、旋转等。最后再用 canvas 导出一个 base64 格式的图片。
可以把 base64 转化成 blob,然后再 append 到一个 formData 里面,下面的函数(来自 b64-to-blob)可以把 base64 转成 blob:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || "";
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, { type: contentType });
return blob;
}
然后就可以 append 到 formData 里面:
1
2
3
let blob = b64toBlob(b64Data, "image/png"),
formData = new FormData();
formData.append("fileContent", blob);