【JS】「多图预警」那些年,被blob虐过的程序猿觉醒了!
前言
本文以图文的方式深入浅出二进制的概念,前面的概念描述较为枯燥,但是非常重要!希望大家耐心往下看,后面有惊喜,定能让您虎躯一震~😉
Blob
其构造函数如下:
new Blob(blobParts, options);
lobParts
:数组类型,可以存放任意多个ArrayBuffer, ArrayBufferView, Blob或者DOMString(会编码为UTF-8),将它们连接起来构成Blob对象的数据。options
:可选项,用于设置blob对象的属性,可以指定如下两个属性:- type:存放到blob中数组内容的MIME类型(默认为"")。
- endings:用于指定包含行结束符n的字符串如何被写入。值为native表示行结束符会被更改为适合宿主操作系统文件系统的换行符(默认值为transparent表示会保持blob中保存的结束符不变)
DOMString 是一个UTF-16字符串。由于JavaScript已经使用了这样的字符串,所以DOMString直接映射到 一个String。
ArrayBuffer(二进制数据缓冲区)、ArrayBufferView(二进制数据缓冲区的array-like视图)
示例如下👇
- 创建一个包含domstring对象的blob对象
const blob = new Blob(['<div>john</div>'], { type: 'text/xml' });console.log(blob); // Blob {size: 15, type: "text/xml"}
- 创建一个包含arraybuffer对象的blob对象
var abf = new ArrayBuffer(8);const blob = new Blob([abf], { type: 'text/plain' });
console.log(blob); // Blob {size: 8, type: "text/plain"}
- 创建一个包含arraybufferview对象的blob对象
var abf = new ArrayBuffer(8);var abv = new Int16Array(abf);
const blob = new Blob(abv, { type: 'text/plain' });
console.log(blob); // Blob {size: 4, type: "text/plain"}
属性
Blob对象有两个属性,参见下表👇:
属性名 | 描述 |
---|---|
size | Blob对象中所包含数据的大小。字节为单位。 只读。 |
type | 一个字符串,表明该Blob对象所包含数据的MIME类型。如果类型未知,则该值为空字符串。 只读。 |
方法
slice(start:number, end:number, contentType:DOMString)
:类似于数组的slice方法,将原始Blob对象按照指定范围分割成新的blob对象并返回,可以用作切片上传- start:开始索引,默认为0
- end:结束索引,默认为最后一个索引
- contentType:新Blob的MIME类型,默认情况下为空字符串
stream()
:返回一个能读取blob内容的ReadableStream。text()
:返回一个Promise对象且包含blob所有内容的UTF-8格式的 USVString。arrayBuffer()
:返回一个Promise 对象且包含blob所有内容的二进制格式的ArrayBuffer。
将blob(或者file)二进制文件保存到formData
进行网络请求(之后可以获取到图片的imageUrl可以用作图片展示或者后续的通过websocket
发送图片地址)
File
File的获取:
下面我们分别使用input和拖放方式选择多张图片操作👇:
- input获取本地文件
<input type="file" multiple id="f" /><script>
var elem = document.getElementById('f');
elem.onchange = function (event) {
var files = event.target.files;
console.log(files); // [{{name: "1.jpg",lastModified: 1594369580771...},{name:'2.jpg',lastModified: 1596012406708...}]
var file = files[0];
console.log(file); // {name: "1.jpg",lastModified: 1594369580771,size: 22344,type: "image/jpeg"...}
console.log(file instanceof File); //true
console.log(files instanceof FileList); // true
/* File继承Blob */
console.log(file.__proto__.__proto__); // Blob {size: 22344, type: ""}
};
</script>
拖放获取
<div id="content" ondrop="drop(event)" ondragover="allowDrop(event);" />
<script>
function allowDrop(ev) {
ev.preventDefault();
}
function drop(ev) {
ev.preventDefault();
const files = ev.dataTransfer.files;
console.log(files); // [{{name: "1.jpg",lastModified: 1594369580771...},{name:'2.jpg',lastModified: 1596012406708...}]
console.log(files instanceof FileList); // true
}
</script>
<style type="text/css">
#content {
width: 500px;
height: 500px;
border: 1px solid brown;
}
</style>
属性
File对象属性,参见下表👇:
属性名 | 描述 |
---|---|
lastModified | 引用文件最后修改日期 |
name | 文件名或文件路径 |
size | 以字节为单位返回文件的大小 |
type | 文件的 MIME 类型 |
方法
File 对象没有自己的实例方法,由于继承了 Blob 对象,因此可以使用 Blob 的实例方法slice()。
数据缓冲区
Buffer
Buffer
是Node.js
提供的对象,前端没有。 它一般应用于IO操作
,例如接收前端请求数据时候,可以通过Buffer相关的API创建一个专门存放二进制数据的缓存区对接收到的前端数据进行整合,一个Buffer类似于一个整数数组,但它对应于V8
堆内存之外的一块原始内存。
ArrayBuffer、ArrayBufferView
ArrayBuffer
先大致看下ArrayBuffer的功能:
ArrayBuffer对象的构造函数如下(length表示ArrayBuffer的长度)👇:
ArrayBuffer(length);
Array和ArrayBuffer的区别👇:
Array | ArrayBuffer |
---|---|
可以放数字、字符串、布尔值以及对象和数组等 | 只能存放0和1组成的二进制数据 |
数据放在堆中 | 数据放在栈中,取数据时更快 |
可以自由增减 | 只读,初始化后固定大小,无论缓冲区是否为空,只能借助TypedArrays、Dataview写入 |
属性
ArrayBuffer对象属性,参见下表👇:
属性名 | 描述 |
---|---|
byteLength | 表示ArrayBuffer的大小 |
方法
slice
:有两个参数👉begin表示起始,end表示结束点。方法返回一个新的 ArrayBuffer ,它的内容是这个ArrayBuffer的字节副本,从begin(包括),到end(不包括)。
ArrayBuffer不能直接操作,而是要通过TypedArray
或DataView
对象来操作,它们会将缓冲区中的数据转换为各种数据类型的数组,并通过这些格式来读写缓冲区的内容。👇
ArrayBufferView
TypedArrays
类型化数组(TypedArrays
)是JavaScript中新出现的一个概念,专为访问原始的二进制数据而生,本质上,类型化数组和ArrayBuffer是一样的,只不过是他具备读写功能
类型数组的类型有:👇:
名称 | 大小 (以字节为单位) | 说明 |
---|---|---|
Int8Array | 1 | 8位有符号整数 |
Uint8Array | 1 | 8位无符号整数 |
Int16Array | 2 | 16位有符号整数 |
Uint16Array | 2 | 16位无符号整数 |
Int32Array | 4 | 32位有符号整数 |
Uint32Array | 4 | 32位无符号整数 |
Float32Array | 4 | 32位浮点数 |
Float64Array | 8 | 64位浮点数 |
类型转换如图👇:
举一些代码例子展示如何转换:
// 创建一个8字节的ArrayBuffervar b = new ArrayBuffer(8);
// 创建一个指向b的视图v1,采用Int32类型,开始于默认的字节索引0,直到缓冲区的末尾
var v1 = new Int32Array(b); // Int32Array(2) [0, 0]
v1[0] = 1
console.log(v1); // Int32Array(2) [1, 0]
// 创建一个指向b的视图v2,采用Uint8类型,开始于字节索引2,直到缓冲区的末尾
var v2 = new Uint8Array(b, 2); // Uint8Array(6) [0, 0, 0, 0, 0, 0]
// 创建一个指向b的视图v3,采用Int16类型,开始于字节索引2,长度为2
var v3 = new Int16Array(b, 2, 2); // Int16Array(2) [0, 0]
TypedArray的应用如何拼接两个音频文件fetch请求音频资源 -> ArrayBuffer -> TypedArray -> 拼接成一个 TypedArray -> ArrayBuffer -> Blob -> Object URL
DataView
创建DataView的语法如下:
var dataView = new DataView(DataView(buffer, byteOffset[可选], byteLength[可选]);
属性
DataView对象有三个属性,参见下表👇:
属性名 | 描述 |
---|---|
buffer | 表示ArrayBuffer |
byteOffset | 指缓冲区开始处的偏移量 |
byteLength | 指缓冲区部分的长度 |
方法
setint8()
:从DataView起始位置以byte为计数的指定偏移量(byteOffset)处存储一个8-bit数(一个字节)getint8()
:从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个8-bit数(一个字节)
除此之外还有getInt16, getUint16, getInt32, getUint32... 使用方法一致,这里就不一一例举
用法如下👇:
let buffer = new ArrayBuffer(32);let dataView = new DataView(buffer,0);
dataView.setInt16(1,56);
dataView.getInt16(1); // 56
FileReader
readAsText(Blob)
:将Blob转化为文本字符串readAsArrayBuffer(Blob)
: 将Blob转为ArrayBuffer格式数据readAsDataURL()
: 将Blob转化为Base64格式的DataURL
使用分别如下👇:
const blob = new Blob(['<xml>foo</xml>'], { type: 'text/xml' });console.log(blob); // Blob(14) {size: 14, type: "text/xml"}
const reader = new FileReader();
reader.onload = () => {
console.log(reader.result);
};
reader.readAsText(blob); // <xml>foo</xml>
reader.readAsArrayBuffer(blob); // ArrayBuffer(14) {}
reader.readAsDataURL(blob); // data:text/xml;base64,PHhtbD5mb288L3htbD4
下面我们尝试把一个文件的内容通过字符串的方式读取出来:
<input type="file" id='f' /><script>
document.getElementById('f').addEventListener('change', function (e) {
var file = this.files[0];
// 首先,需要创建一个FileReader的实例。
const reader = new FileReader();
reader.onload = function () {
// 在加载完成时回调
const content = reader.result;
console.log(content);
}
reader.readAsText(file); // 将blob转化为文本字符串读取
}, false);
</script>
读取结果如下👇:
BlobURL
图像展示👇:
<div id="content"><input type="file" multiple id="f" />
</div>
<script>
const elem = document.getElementById('f');
const content = document.getElementById('content');
// 根据不同浏览器封装一个转换BlobUrl的方法:file可以是File对象也可以是Blob对象
const getObjectURL = (file) => {
let url;
if (window.createObjectURL) {
url = window.createObjectURL(file);
} else if (window.URL) {
url = window.URL.createObjectURL(file);
} else if (window.webkitURL) {
url = window.webkitURL.createObjectURL(file);
}
return url;
};
elem.onchange = function (event) {
const files = event.target.files;
const file = files[0];
const img = document.createElement('img');
img.src = getObjectURL(file);
content.appendChild(img);
};
</script>
我们查看demo页面这个mm图片元素,会发现其URL地址既不是传统HTTP,也不是Base64 URL,而是blob:开头的字符串,可以通过将其放在地址栏中进行检查。
文件下载👇:
<body><button onclick="download()">download.txt</button>
<script>
const getObjectURL = (file) => {
let url;
if (window.createObjectURL) {
url = window.createObjectURL(file);
} else if (window.URL) {
url = window.URL.createObjectURL(file);
} else if (window.webkitURL) {
url = window.webkitURL.createObjectURL(file);
}
return url;
};
function download() {
const fileName = 'download.txt';
const myBlob = new Blob(['johnYu'], { type: 'text/plain' });
downloadFun(fileName, myBlob);
}
function downloadFun(fileName, blob) {
const link = document.createElement('a');
link.href = getObjectURL(blob);
link.download = fileName;
link.click();
link.remove();
URL.revokeObjectURL(link.href);
}
</script>
</body>
点击按钮下载文档,文档内容为:johnYu
这里不调用revokeObjectURL
时访问chrome://blob-internals/
可以看到当前内部的blob文件列表:
不再使用的BlobUrl后续会自动清除(关闭浏览器也会自动清除),但是最好使用URL.revokeObjectURL(url)
手动清除它们:
URL.revokeObjectURL('blob:http://127.0.0.1:5500/d2a9a812-0dbf-41c5-a96b-b6384d33f281');
执行后再次访问chrome://blob-internals/
可以看到文件已经被清除
dataURL
其语法格式格式如下👇:
data:[<mediatype>][;base64],data
data
:前缀mediatype
表明数据类型,是一个MIME类型字符串,如image/jpeg表示一个JPEG图片文件。如果省略,默认值为text/plain;charset=US-ASCII。base64
:标志位(如果是文本,则可选)data
:数据本身
如何获取DataUrl
- 上面示例中使用的方法readAsDataURL()就是将Blob转化为Base64格式的DataUrl;
- 使用原生
Web API
编码/解码
btoa('hello base64') // "PHhtbD5mb288L3htbD4="atob('PHhtbD5mb288L3htbD4=') // "<xml>foo</xml>"
- atob(): 负责解码已经使用base64编码了的字符串。
- btoa(): 将二进制字符串转为base64编码的
ASCII
字符串。
- Canvas的toDataURL方法:
<body><canvas id="canvas" width="200" height="50"></canvas>
<textarea id="content"></textarea>
<script>
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
// canvas的绘制
ctx.font = 'Bold 20px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = 'purple';
ctx.fillText('johnYu', 10, 30);
// 获取 Data URL
document.getElementById('content').value = canvas.toDataURL();
}
</script>
</body>
如下图所示,文本框中的内容即为canvas中绘制内容的base64格式。
如果我们将前面的返回结果data:text/xml;base64,PHhtbD5mb288L3htbD4=
放在浏览器的地址栏中,则可以看到显示的内容。
DataUrl的使用
- 由于可以将其用作URL的替代,因此DataURL和BlobUrl一样可以在script/img/video/iframe等标签的src属性和background的url中使用,用法与BlobUrl基本一致,只需要将前面的
elem.onchange
做如下改造
<body><div id="content">
<input type="file" multiple id="f" />
</div>
<script>
const elem = document.getElementById('f');
const content = document.getElementById('content');
elem.onchange = function (event) {
const files = event.target.files;
const file = files[0];
const img = document.createElement('img');
- img.src = getObjectURL(file);
+ const reader = new FileReader();
+ reader.onload = function () {
+ img.src = reader.result;
+ };
+ reader.readAsDataURL(file);
content.appendChild(img);
};
</script>
</body>
- 由于数据本身由URL表示,因此可以将其保存在Cookie中传递给服务器。
- 当图片的体积太小,占用一个HTTP会话不是很值得时。
- 当访问外部资源很麻烦或受限时
DataUrl不会被浏览器缓存,但是小部分会通过css缓存,在下面例子中,DataUrl的使用是完全符合场景的。它避免了让这个小小的背景图片独自产生一次HTTP请求,而且,这个小图片还能同CSS文件一起被浏览器缓存起来,重复使 用,不会每次使用时都加载一次。只要这个图片不是很大,而且不是在CSS文件里反复使用,就可以DataUrl方法呈现图片降低页面的加载时间,改善用户的浏览体验。
background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");
- 作为下载连接使用
<script>const createDownload = (fileName, content) => {
const blob = new Blob([content]);
const reader = new FileReader();
const link = document.createElement('a');
link.innerHTML = fileName;
link.download = fileName;
reader.onload = () => {
link.href = reader.result;
document.getElementsByTagName('body')[0].appendChild(link);
};
reader.readAsDataURL(blob);
};
createDownload('download.txt', 'johnYu');
</script>
点击a标签后后下载文本内容为johnYu的txt文件,在下面的BlobURL同样可以实现👇
区别
BlobURL基本用法与DataUrl相同,都可以通过将其放在地址栏中进行检查也可以用作普通URL使用。
但是,存在以下差异。
- BlobUrl始终是唯一字符串,即时你每次传递相同的Blob,每次也会生成不同的BlobUrl;DataUrl值跟随blob变化;
- 就BlobUrl而言,它并不代表数据本身,数据存储在浏览器中,BlobUrl只是访问它的key。数据会一直有效,直到关闭浏览器或者手动清除。而DataUrl是直接编码的数据本身。因此即使将BlobUrl传递给服务器等也无法访问数据。关闭浏览器后仍然可以在地址栏访问后DataUrl,但是访问不到BlobUrl
- BlobUrl的长度一般比较短,但DataUrl因为直接存储图片base64编码后的数据,往往很长(Base64编码的数据体积通常会比二进制格式的图片体积大1/3。),因此当显式大图片时,使用BlobUrl能获取更好的可能性,速度和内存比DataUrl更有效
- BlobUrl可以方便的使用XMLHttpRequest获取源数据(xhr.responseType = 'blob')。对于DataUrl,并不是所有浏览器都支持通过XMLHttpRequest获取源数据的
<body><button onclick="download1()">XMLHttpRequest下载</button>
<button onclick="download2()">fetch下载</button>
<img id="img" />
<script>
var eleAppend = document.getElementById('forAppend');
const url = 'https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/9ecb4e119c26e64b8b4ec5258f159b3b~300x300.image';
const pingan = document.querySelector('#pingan');
function download1() {
const xhr = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.responseType = 'blob';
xhr.onload = function () {
if (this.status == 200) {
renderImage(this.response);
}
};
xhr.send(null);
}
function download2() {
fetch(url)
.then((res) => {
return res.blob();
})
.then((myBlob) => {
renderImage(myBlob);
});
}
function renderImage(blob) {
window.URL = window.URL || window.webkitURL;
var img = document.getElementById('img');
img.onload = function (e) {
window.URL.revokeObjectURL(img.src); // 清除释放
};
img.src = window.URL.createObjectURL(blob);
}
</script>
</body>
- BlobUrl除了可以用作图片资源的网络地址,BlobUrl也可以用作其他资源的网络地址,例如html文件、json文件等,为了保证浏览器能正确的解析BlobUrl返回的文件类型,需要在创建Blob对象时指定相应的type
const createDownload = (fileName, content) => {const blob = new Blob([content], { type: 'text/html' });
const link = document.createElement('a');
link.innerHTML = fileName;
link.download = fileName;
link.href = getObjectURL(blob);
document.getElementsByTagName('body')[0].appendChild(link);
};
createDownload('download.html', '<button>foo</button>');
- DataUrl不会被浏览器缓存,这意味着每次访问这样页面时都被下载一次。这是一个使用效率方面的问题——尤其当这个图片被整个网站大量使用的时候。但是小部分可以通过css缓存
canvas
方法
toDataURL(type, encoderOptions))
:以指定格式返回 DataUrl,该方法接收两个可选参数- type:表示图片格式,默认为 image/png
- encoderOptions:表示图片的质量,在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92,其他参数会被忽略。
toBlob(callback, type, encoderOptions)
:创造Blob对象, 用于展示canvas的图片,默认图片类型是image/png,分辨率是96dpi- callback: 参数是blob对象的回调函数
getImageData(x,y,width,height)
:返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。- x: 开始复制的左上角位置的 x 坐标。
- y: 开始复制的左上角位置的 y 坐标。
- width: 将要复制的矩形区域的宽度。
- height: 将要复制的矩形区域的高度。
putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
:将图像数据(从指定的 ImageData 对象)放回画布上。- imgData: 规定要放回画布的 ImageData 对象。
- x: ImageData 对象左上角的 x 坐标,以像素计。
- y: ImageData 对象左上角的 y 坐标,以像素计。
- dirtyX: 可选。水平值(x),以像素计,在画布上放置图像的位置。
- dirtyY: 可选。水平值(y),以像素计,在画布上放置图像的位置。
- dirtyWidth: 可选。在画布上绘制图像所使用的宽度。
- dirtyHeight: 可选。在画布上绘制图像所使用的高度。
应用场景
当我们需要获取到canvas的内容,可以用到toDataURL
和toBlob
属性(可用于签名,图片剪裁,图片压缩等场景),putImageData
、getImageData
可以用于图片灰度或者复制时使用(见后面的使用场景章节👇)
获取内容:
<body><div id="content">
<button onclick="drawnImg()">绘制图像</button>
<button onclick="getImg()">获取图像</button>
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
<img alt="" />
</div>
<script>
var drawing = document.getElementById('drawing');
var quality = 0.3;
const imgType = 'image/jpeg';
var drawnImg = function () {
if (drawing.getContext) {
var context = drawing.getContext('2d');
//取得图像的数据 URI
var image = document.images[0];
context.drawImage(image, 20, 20, 100, 100);
}
};
var getImg = async function () {
const content = getContent('base64');
console.log(content);
const content1 = await getContent('file');
console.log(content1);
};
var getContent = function (type) {
switch (type) {
case 'base64':
{
const imgURL = drawing.toDataURL(imgType, quality);
return imgURL;
}
break;
case 'file':
{
// 转为文件格式
return new Promise((resolve) => {
drawing.toBlob(
(blob) => {
resolve(blob);
},
imgType,
quality
);
});
}
break;
}
};
</script>
</body>
关系及转换
字符串 → Uint8Array
var str = 'ab';console.log(Uint8Array.from(str.split(''), (e) => e.charCodeAt(0))); // Uint8Array(2) [97, 98]
Uint8Array → 字符串
var u8 = Uint8Array.of(97, 98);console.log(Array.from(u8, (e) => String.fromCharCode(e)).join('')); // ab
字符串 → DataUrl
var str = 'ab';console.log('data:application/octet-stream;base64,' + btoa(str)); // data:application/octet-stream;base64,YWI=
DataUrl -> 字符串
var data = 'data:application/octet-stream;base64,YWI=';console.log(atob(data.split(',')[1])); // ab
Uint8Array -> ArrayBuffer
var u8 = Uint8Array.of(1, 2);console.log(u8.buffer); // ArrayBuffer(2) {}
ArrayBuffer -> Uint8Array
var buffer = new ArrayBuffer(2);console.log(new Uint8Array(buffer)); // Uint8Array(2) [0, 0]
ArrayBuffer -> DataView
var buffer = new ArrayBuffer(2);var dataView = new DataView(buffer, 0); // DataView(2) {}
DataView -> ArrayBuffer
console.log(dataView.buffer); // ArrayBuffer(2) {}
ArrayBuffer → Blob
var buffer = new ArrayBuffer(32);var blob = new Blob([buffer]); // Blob {size: 32, type: ""}
UintXXArray → Blob
var u8 = Uint8Array.of(97, 32, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33);var blob = new Blob([u8]);
字符串 → Blob
var blob = new Blob(['Hello World!'], {type: 'text/plain'}); // Blob {size: 12, type: "text/plain"}
DataUrl -> blob
var data = 'data:application/octet-stream;base64,YWI=';function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
console.log(dataURLtoBlob(data)); // Blob {size: 2, type: "application/octet-stream"}
Blob →
var blob = new Blob(['a Hello world!'], { type: 'text/plain' });var reader = new FileReader();
reader.readAsText(blob, 'utf-8');
reader.onload = function (e) {
console.info(reader.result); // a Hello world!
};
reader.onerror = function (e) {
console.error(reader.error);
};
可以用promise做多次转换
var blob = new Blob(['a Hello world!'], { type: 'text/plain' });function read(blob) {
var fr = new FileReader();
var pr = new Promise((resolve, reject) => {
fr.onload = (eve) => {
resolve(fr.result);
};
fr.onerror = (eve) => {
reject(fr.error);
};
});
return {
arrayBuffer() {
fr.readAsArrayBuffer(blob);
return pr;
},
binaryString() {
fr.readAsBinaryString(blob);
return pr;
},
dataURL() {
fr.readAsDataURL(blob);
return pr;
},
text() {
fr.readAsText(blob);
return pr;
},
};
}
var pstr1 = read(blob).binaryString();
var pstr2 = read(blob)
.arrayBuffer()
.then((e) => Array.from(new Uint8Array(e), (e) => String.fromCharCode(e)).join(''));
Promise.all([pstr1, pstr2]).then((e) => {
console.log(e[0]); // a Hello world!
console.log(e[0] === e[1]); // true
});![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/78f4c53fd471419fb10f3c803a4dd94a~tplv-k3u1fbpfcp-watermark.image)
应用场景
图像灰度化
<body><button onclick="drawngray()">黑白图片</button>
<img alt="" />
<canvas id="myCanvas">canvas</canvas>
<script>
var drawngray = function () {
var myCanvas = document.getElementById('myCanvas');
if (myCanvas.getContext) {
var context = myCanvas.getContext('2d');
var image = document.images[0];
// 动态设置canvas的大小
myCanvas.width = image.width;
myCanvas.height = image.height;
var imageData, data, i, len, average, red, green, blue, alpha;
//绘制原始图像
context.drawImage(image, 0, 0);
//取得图像数据
imageData = context.getImageData(0, 0, image.width, image.height);
data = imageData.data;
for (i = 0, len = data.length; i < len; i += 4) {
red = data[i];
green = data[i + 1];
blue = data[i + 2];
// alpha = data[i + 3];
//求得 rgb 平均值
average = Math.floor((red + green + blue) / 3);
//设置颜色值,透明度不变
data[i] = average;
data[i + 1] = average;
data[i + 2] = average;
}
//回写图像数据并显示结果
imageData.data = data;
context.putImageData(imageData, 0, 0);
}
};
</script>
</body>
除次之外getImageData和putImageData还可以用作cavas图片复制:https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_getimagedata
图片压缩
compress.js
const MAX_WIDTH = 800; // 图片最大宽度function compress(base64, quality, mimeType) {
let canvas = document.createElement('canvas');
let img = document.createElement('img');
img.crossOrigin = 'anonymous';
return new Promise((resolve, reject) => {
img.src = base64;
img.onload = () => {
let targetWidth, targetHeight;
if (img.width > MAX_WIDTH) {
targetWidth = MAX_WIDTH;
targetHeight = (img.height * MAX_WIDTH) / img.width;
} else {
targetWidth = img.width;
targetHeight = img.height;
}
canvas.width = targetWidth;
canvas.height = targetHeight;
let ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除画布
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 通过toDataURL压缩后的base64
let imageData = canvas.toDataURL(mimeType, quality / 100);
resolve(imageData);
};
});
}
test.html
<body><input type="file" accept="image/*" onchange="loadFile(event)" />
<script></script>
<script>
function dataUrlToBlob(base64) {
var arr = base64.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
function uploadFile(url, blob) {
let formData = new FormData();
let request = new XMLHttpRequest();
// 封装到FormData中进行文件的上传
formData.append('image', blob);
request.open('POST', url, true);
request.send(formData);
}
const loadFile = function (event) {
const reader = new FileReader();
reader.onload = async function () {
let compressedDataURL = await compress(reader.result, 90, 'image/jpeg');
// 压缩后将base64转为Blob 对象减少传输数据量
let compressedImageBlob = dataUrlToBlob(compressedDataURL);
uploadFile('https://httpbin.org/post', compressedImageBlob);
};
// 获取用户选取的图片文件,通过FileReader转化成base64
reader.readAsDataURL(event.target.files[0]);
};
</script>
</body>
分片上传
<body><input type="file" name="file" onchange="selfile();" />
<script>
const url = 'https://httpbin.org/post';
/**
* @param file 原始文件
* @param chunkSize 默认每次上传分片大小
*/
async function chunkedUpload(file, chunkSize = 1024 * 1024 * 5) {
// 将文件拆分成chunkSize大小的分块,然后每次请求只需要上传这一个部分的分块即可
for (let start = 0; start < file.size; start += chunkSize) {
// File对象继承自Blob对象,因此可以使用slice方法对大文件进行切
const chunk = file.slice(start, start + chunkSize + 1);
const fd = new FormData();
fd.append('data', chunk);
await fetch(url, { method: 'post', body: fd })
.then((res) => res.text())
.then((res) => console.log(res)); // 打印上传结果
}
}
function selfile() {
let file = document.querySelector('[name=file]').files[0];
// 自定义分片大小
const LENGTH = 1024 * 1024 * 1;
chunkedUpload(file, LENGTH);
}
</script>
</body>
服务器接收到这些切片后,再将他们拼接起来就可以了,下面是PHP拼接切片的示例代码:
$filename = './upload/' . $_POST['filename'];//确定上传的文件名//第一次上传时没有文件,就创建文件,此后上传只需要把数据追加到此文件中
if(!file_exists($filename)){
move_uploaded_file($_FILES['file']['tmp_name'],$filename);
}else{
file_put_contents($filename,file_get_contents($_FILES['file']['tmp_name']),FILE_APPEND);
echo $filename;
}
测试时记得修改nginx的server配置,否则大文件可能会提示413 Request Entity Too Large的错误。
server {// ...
client_max_body_size 50m;
}
参考文章 📜
❤️ 理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型
❤️ 聊聊JS的二进制家族:Blob、ArrayBuffer和Buffer
❤️ 你不知道的 Blob
扩展 🏆
如果你觉得本文对你有帮助,可以查看我的其他文章❤️:
👍 vue3实战笔记 | 快速入门🚀
👍 10个简单的技巧让你的 vue.js 代码更优雅🍊
👍 零距离接触websocket🚀
👍 Web开发应了解的5种设计模式
👍 Web开发应该知道的数据结构
👍 如何在JavaScript中获取屏幕,窗口和网页大小
以上是 【JS】「多图预警」那些年,被blob虐过的程序猿觉醒了! 的全部内容, 来源链接: utcz.com/a/97657.html