前端Blob对象
Blob 对象表示一个不可变、原始数据的类文件对象。Blob对象中的数据并不一定得是JavaScript中的原生形式。File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件。
Blob(Binary Large Object)对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的 API(比如 File 对象),都是建立在 Blob 对象基础上的,继承了它的属性和方法。
通过构造函数创建Blob对象
var blob = new Blob(dataArr:Array, opt:{type:string});
- dataArray:数组,包含了要添加到Blob对象中的数据,数据可以是任意多个ArrayBuffer,ArrayBufferView, Blob,或者 DOMString对象。
- opt:对象,用于设置Blob对象的属性(如:MIME类型)
1、创建一个装填DOMString对象的Blob对象
2、创建一个装填ArrayBuffer对象的Blob对象
3、创建一个装填ArrayBufferView对象的Blob对象(ArrayBufferView可基于ArrayBuffer创建,返回值是一个类数组。如下:创建一个8字节的ArrayBuffer,在其上创建一个每个数组元素为2字节的“视图”)
Javascript 之 ArrayBuffer: www.cnblogs.com/chris-oil/p…
slice() 方法原本接受 length 作为第二个参数,以表示复制到新 Blob 对象的字节数。如果设置的参数使 start + length 超出了源 Blob 对象的大小,则返回从开始到结尾的所有数据。
slice() 方法在某些浏览器和版本上带有浏览器引擎前缀:比如 Firefox 12 及更早版本的blob.mozSlice() 和 Safari 中的blob.webkitSlice()。 没有浏览器引擎前缀的老版本 slice() 方法有不同的语义,并且已过时。Firefox 30 取消了对 blob.mozSlice() 的支持。
属性
-
Blob.isClosed (只读)
布尔值,指示 Blob.close() 是否在该对象上调用过。 关闭的 blob 对象不可读。
-
Blob.size (只读)
Blob 对象中所包含数据的大小(字节)。
-
Blob.type (只读)
一个字符串,表明该Blob对象所包含数据的MIME类型。如果类型未知,则该值为空字符串。
方法
-
Blob.close()
关闭 Blob 对象,以便能释放底层资源。
-
Blob.slice([start[, end[, contentType]]])
返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。其实就是对这个blob中的数据进行切割,我们在对文件进行分片上传的时候需要使用到这个方法。
看到上面这些方法和属性,使用过HTML5提供的File接口的应该都很熟悉,这些属性和方法在File接口中也都有。 其实File接口就是基于Blob,继承blob功能并将其扩展为支持用户系统上的文件,也就是说:
File接口中的Flie对象就是继承与Blob对象。
使用场景
1、文件分片上传
首先说说分片上传,我们在进行文件上传的时候,因为服务器的限制,会限制每一次上传到服务器的文件大小不会很大,这个时候我们就需要把一个需要上传的文件进行切割,然后分别进行上传到服务器。
假如需要做到这一步,我们需要解决两个问题:
- 怎么切割?
- 怎么得知当前传输的进度?
首先怎么切割的问题上面已经有过说明,因为File文件对象是继承与Blob对象的,因此File文件对象也拥有slice这个方法,我们可以使用这个方法将任何一个File文件进行切割。
代码如下:
var BYTES_PER_CHUNK = 1024 * 1024; // 每个文件切片大小定为1MB .
var blob = document.getElementById("file").files[0];
var slices = Math.ceil(blob.size / BYTES_PER_CHUNK);
var blobs = [];
slices.forEach(function(item, index) {
blobs.push(blob.slice(index,index + 1));
});
通过上面的方法。我们就得到了一个切割之后的File对象组成的数组blobs;
接下来要做的时候就是讲这些文件分别上传到服务器。
在HTTP1.1以上的协议中,有Transfer-Encoding这个编码协议,用以和服务器通信,来得知当前分片传递的文件进程。
这样解决了这两个问题,我们不仅可以对文件进行分片上传,并且能够得到文件上传的进度。
Transfer-Encoding: blog.csdn.net/liuxiao7238…
2、粘贴图片
blob还有一个应用场景,就是获取剪切板上的数据来进行粘贴的操作。例如通过QQ截图后,需要在网页上进行粘贴操作。
粘贴图片我们需要解决下面几个问题
- 监听用户的粘贴操作
- 获取到剪切板上的数据
- 将获取到的数据渲染到网页中
首先我们可以通过paste事件来监听用户的粘贴操作:
document.addEventListener('paste', function (e) {
console.info(e);
});
然后通过事件对象中的clipboardData 对象来获取图片的文件数据。
clipboard对象: www.ruanyifeng.com/blog/2021/0…
clipboardData对象介绍
介绍一下 clipboardData 对象,它实际上是一个 DataTransfer 类型的对象, DataTransfer 是拖动产生的一个对象,但实际上粘贴事件也是它。
clipboardData 的属性介绍
| 属性 | 类型 | 说明 |
|---|---|---|
| dropEffect | String | 默认是 none |
| effectAllowed | String | 默认是 uninitialized |
| files | FileList | 粘贴操作为空List |
| items | DataTransferItemList | 剪切板中的各项数据 |
| types | Array | 剪切板中的数据类型 该属性在Safari下比较混乱 |
items 介绍
items 是一个 DataTransferItemList 对象,自然里面都是 DataTransferItem 类型的数据了。
属性
items 的 DataTransferItem 有两个属性 kind 和 type
| 属性 | 说明 |
|---|---|
| kind | 一般为 string 或者 file |
| type | 具体的数据类型,例如具体是哪种类型字符串或者哪种类型的文件,即 MIME-Type |
方法
| 方法 | 参数 | 说明 |
|---|---|---|
| getAsFile | 空 | 如果 kind 是 file ,可以用该方法获取到文件 |
| getAsString | function(str) | 如果 kind 是 string ,可以用该方法获取到字符串str |
在原型上还有一些其他方法,不过在处理剪切板操作的时候一般用不到了。
type 介绍
一般 types 中常见的值有 text/plain 、 text/html 、 Files 。
| 值 | 说明 |
|---|---|
| text/plain | 普通字符串 |
| text/html | 带有样式的html |
| Files | 文件(例如剪切板中的数据) |
有了上面这些方法,我们可以解决第二个问题即获取到剪切板上的数据。
document.addEventListener('paste', function (e) {
console.info(e);
const cbd = e.clipboardData;
for(let i = 0; i < cbd.items.length; i++) {
const item = cbd.items[i];
console.info(item);
if(item.kind == "file"){
const blob = item.getAsFile();
if (blob.size === 0) {
return;
}
console.info(blob);
}
}
});
最后我们需要将获取到的数据渲染到网页上。
其实这个本质上就是一个类似于上传图片本地浏览的问题。我们可以直接通过HTML5的File接口将获取到的文件上传到服务器然后通过讲服务器返回的url地址来对图片进行渲染。也可以使用fileRender对象来进行图片本地浏览。
fileRender对象简介
从Blob中读取内容的唯一方法是使用 FileReader。
FileReader接口有4个方法,其中3个用来读取文件,另一个用来中断读取。无论读取成功或失败,方法并不会返回读取结果,这一结果存储在result属性中。
| 方法名 | 参数 | 描述 |
|---|---|---|
| readAsBinaryString | file | 将文件读取为二进制编码 |
| readAsText | file,[encoding] | 将文件读取为文本 |
| readAsDataURL | file | 将文件读取为DataURL |
| abort | (none) | 终端读取操作 |
FileReader接口包含了一套完整的事件模型,用于捕获读取文件时的状态。
| 事件 | 描述 |
|---|---|
| onabort | 中断 |
| onerror | 出错 |
| onloadstart | 开始 |
| onprogress | 正在读取 |
| onload | 成功读取 |
| onloadend | 读取完成,无论成功失败 |
通过上面的方法以及事件,我们可以发现,通过readAsDataURL方法及onload事件就可以拿到一个可本地浏览图片的DataURL。
最终代码如下:
document.addEventListener('paste', function (e) {
console.info(e);
const cbd = e.clipboardData;
const fr = new FileReader();
for(let i = 0; i < cbd.items.length; i++) {
const item = cbd.items[i];
console.info(item);
if(item.kind == "file"){
const blob = item.getAsFile();
if (blob.size === 0) {
return;
}
console.info(blob);
fr.readAsDataURL(blob);
fr.onload = function(e){
var result=document.getElementById("paste");
//显示文件
result.innerHTML='<img src="' + this.result +'" alt="" />';
}
}
}
});
前端用JS实现粘贴图片实现上传图片功能:www.freesion.com/article/923…
演示:
1、创建一个文本类型的Blob对象
const str = '123';
const blob = new Blob([str], {
type: 'text/plain'
})
console.log(blob);
// 读取文本
blob.text().then(res => console.log(res))
2、文件下载与图片预览
<a id="btn">下载</a>
<input type="file" id="input" />
const str = `<div>hello</div>`
const blob = new Blob([str], {
type: 'text/html'
})
btn.onclick = function(e){
this.setAttribute('download', '123.html')
// 将Blob对象转化为URL地址
this.href = URL.createObjectURL(blob);
}
input.onchange = function(e){
const file = e.target.files[0];
console.log(file);
// 文件下载
const a = document.createElement('a');
a.setAttribute('download', 'myDownload.html');
a.click();
// 图片预览
const img = new Image();
img.src = URL.createObjectURL(file);
document.body.appendChild(img);
// 异步图片预览
const img = new Image();
const fileRead = new FileReader();
fileRead.onload = function(){
img.src = fileRead.result;
}
fileRead.readAsDataURL(file);
}
3、文件切片上传与合并
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>fileSlice</title>
</head>
<body>
<input type="file" id="input" >
<button id="btn">切片上传</button>
<script>
let chunkSize = 1024, // 1kb
index = 0;
btn.addEventListener('click', upload);
function upload(){
console.log(input.files[0]);
const file = input.files[0];
const [filename, ext] = file.name.split('.');
let start = index * chunkSize;
if (start > file.size) {
const fullName = file.name;
fetch(`http://127.0.0.1:1000/merge?total=${index}&fullName=${fullName}`).
then(() => {
console.log('合并完成');
})
return;
}
const blob = file.slice(start, start + chunkSize);
// console.log("blob", blob);
const blobName = `${filename}${index}.${ext}`
const blobFile = new File([blob], blobName);
console.log("file", blobFile);
const formData = new FormData();
formData.append('file', blobFile);
fetch('http://127.0.0.1:1000/upload', {
method: 'post',
body: formData
}).then(() => {
index ++;
upload();
})
}
</script>
</body>
</html>
const express = require('express');
const App = express();
const Path = require('path');
const fs = require('fs');
const multiparty = require('multiparty');
App.use(express.static(Path.join(__dirname, 'public')));
App.use(express.static(Path.join(__dirname, 'uploads')));
App.all('*',function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
next();
});
App.post('/upload', function(req, res){
/* 生成multiparty对象,并配置上传目标路径 */
let form = new multiparty.Form({
uploadDir: 'uploads'
});
form.parse(req, function (err, fields, files) {
try {
let inputFile = files.file[0];
let newPath = form.uploadDir + "/" + inputFile.originalFilename;
// 同步重命名文件名 会重新写入文件
fs.renameSync(inputFile.path, newPath);
res.send({ data: "上传成功!" });
} catch (err) {
console.log(err);
res.send({ err: "上传失败!" });
};
})
});
App.get('/merge', function(req, res) {
console.log(req.query.total);
const { total, fullName } = req.query;
console.log("fullname", fullName);
const [filename, ext] = fullName.split('.');
for (let i = 0; i < total; i++) {
// 追加写入到文件中
fs.appendFileSync(`./uploads/${fullName}`, fs.readFileSync(`./uploads/${filename}${i}.${ext}`));
// 删除本次使用的chunk
fs.unlinkSync(`./uploads/${filename}${i}.${ext}`);
}
res.send({data: '合并成功'});
})
App.listen(1000, function(){
console.log('Files Server listening on port 1000');
});
4、图片粘贴
<div id="paste" style="width: 200px; height: 200px; background-color: antiquewhite;"></div>
<script>
document.addEventListener('paste', function (e) {
console.info(e);
const cbd = e.clipboardData;
const fr = new FileReader();
for(let i = 0; i < cbd.items.length; i++) {
const item = cbd.items[i];
console.info(item);
if(item.kind == "file"){
const blob = item.getAsFile();
if (blob.size === 0) {
return;
}
console.info(blob);
fr.readAsDataURL(blob);
fr.onload = function(e){
var result=document.getElementById("paste");
//显示文件
result.innerHTML='<img src="' + this.result +'" alt="" />';
}
}
}
});
</script>