前端下载文件以及上传图片预览,顺便了解arrayBuffer和blob
TL;DR
前端下载的关键靠下载图片
后端传blob,前端处理blob,然后下载文件
二进制数组arrayBuffer、typedArray、dataView相关内容
blob、file相关内容
上传图片预览的两种方式:blobURL和dataURL
下载的关键----a的download属性
想要实现点击加载,就能下载文件的功能。 会常常用到a标签的down属性。
比如查看某图片查看图片
,这个代码常见,但是点击会打开一个新页面,所以关键点就是下载图片
,这里点击后就会弹出对话框保存在哪,以及编辑默认文件名。如果需要另外指定文件名的话,可以用download="demo_1.jpg"
。
需要注意,下载文件的网页打开需要http开头不能file开头,也就是本地测试的时候,需要启动本地服务。
前端实现下载任意服务器的任意的文件
经常可能需要下载pdf excel jpg
之类的。其实可以同种操作。 如果不了解二进制数组和blob,需要看后面。
// 前端,建个index.html文件放进这些,启动本地服务(node server.js) // fetch的方式 fetch("/down") .then(res => res.blob()) .then(blob => { down(blob, "demo.pdf"); }); // 普通的方式 request("/down").then(blob => { var blob = new Blob([blob]); down(blob, "demo1.pdf"); }); function down(blob, filename) { var blobURL = window.URL.createObjectURL(blob); var a = document.createElement("a"); a.href = blobURL; // 表示下载的 a.download = filename; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(blobURL); } function request(url, method = "GET") { return new Promise((resolve, reject) => { var xhr = new XMLHttpRequest(); xhr.open(method, url); // !!! 敲黑板 如果后端传过来的时候blob或者buffer之类的 这句必须加 xhr.responseType = "arraybuffer"; xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { resolve(xhr.response); } }; xhr.send(); }); } // -------- 后端 ------- // 引入自己的express路径 不用express当然也行 这就是server.js const express = require('/usr/local/lib/node_modules/express') let app = express() app.listen(7777) app.get('/',(req,res)=>{ res.sendFile(path.join(__dirname,'index.html')) }) app.get('/down',(req,res)=>{ // 注意必须有一个demo.pdf的文件 res.download('demo.pdf') // 浏览器打开 localhost:7777 没出错的话就能下载文件了
其实说到这里,再来一个小玩法,比如后端传过来一串buffer怎么玩。
// 比如后端加一个这个 app.get('/buffer',(req,res)=>{ res.send(Buffer.from('花花天下第一美')) }) // 猜前端怎么玩 request("/buffer").then(response => { // 提取uint8Array let uint8 = new Uint8Array(response); // 解决乱码 let resToString = decodeURIComponent( escape(String.fromCharCode(...uint8)) ); // 花花天下第一美 console.log(resToString) });
ArrayBuffer
最早出现ArrayBuffer的原因是,js要和显卡进行数据传输(webGL),需要二进制数据类型。由此诞生二进制数组。
二进制数组由三类对象组成。
ArrayBuffer
对象:代表原始的二进制数据。是内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。常用的属性byteLength
,如果分配的内存区域大,需要用这个属性检测有没有这样的空间。常用的方法slice
,拷贝生成一个新的ArrayBuffer对象。还有isView()
判断是不是下面两种实例。
TypedArray
视图:用来读写简单类型的二进制数据。共包括9种类型的视图,比如Uint8Array(无符号8位整数)数组视图, Int16Array(16位整数)数组视图, Float32Array(32位浮点数)数组视图等等。
DataView
视图:用来读写复杂类型的二进制数据。可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号8位整数)、第二、三个字节是 Int16(16位整数)、第四个字节开始是 Float32(32位浮点数)等等,此外还可以自定义字节序。
特点:二进制数组并不是真正的数组,而是类似数组的对象。注意的是,两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。
// 创建一个8字节的ArrayBuffer,4个格子,每个格子是一个1个字节就是8位,就是8个由0或者1组成的数,也就是最大的存储是255, var buffer = new ArrayBuffer(4) // [0,0,0,0] console.log(buffer) // 1个格子就是8位,最大值依然255 var x1 = new Uint8Array(buffer) // [0,0,0,0] console.log(x1) // 00000000 00000000 00000000 000000001 注意顺序从右往左 但是数组显示是从右往左,自己脑子绕过来[1,0,0,0] x1[0] = 1 // 00000000 00000000 00000001 000000001 [1,1,0,0] x1[1] = 1 // [1,1,0,0] console.log(x1) // 1个格子16位,就是占据2份8位的格子,每一项要*256 var x2 = new Uint16Array(buffer) // 0000000000000000 00000001000000001 [257,0] 这样后面的就能表示更多的2次方,最大能256*256-1=65535 console.log(x2) // 0000000000000000 00000000000000110 x2[1] = 6 console.log(x2) // 000000000000000000000000000000110 更厉害了,不赘述 var x3 = new Uint32Array(buffer) // 111111111111111111111111111111111 [4294967295] x3[0] = 4294967295 console.log(x3) // 11111111 1111111 111111 1111111 [255,255,255,255] console.log(x1) // 看看dataView new DataView(buffer [, byteOffset [, byteLength]]) 第几个buffer开始,长度 // view相当于 截取某段内存空间 然后以各种形式展示数据 // 00000000【3】 00000000【2】 00000000【1】 00000000【0】 var buffer = new ArrayBuffer(4) // 同上 var view1 = new DataView(buffer) // 上面的左边第一个 00000000 【3】 00000000【2】从索引2开始取2个个,也就是索引2和索引3 var view2 = new DataView(buffer,2,2) // 相当于 In8Array[2] = 42 00000010【3】 00000001【2】 00000000【1】 00000000【0】 view1.setInt8(2,1) view1.setInt8(3,2) // 相当于 In8Array[0] 00000010【3】 00000001【2】 也就是1 console.log(view2.getInt8(0))
ArrayBuffer的来源:
new ArrayBuffer(8)
从本地文件读取,利用FileReader.readAsArrayBuffer()
,开始读取指定的 Blob(也就是file)中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象
从base64 字符串获取
通过ajax请求获取 设置responseType 为 ArrayBuffer 类型
// 举个ArrayBuffer的实例吧,发送使用XMLhttpRequest发送ArrayBuffer数据: function sendArrayBuffer() { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... }; var uInt8Array = new Uint8Array([1, 2, 3]); // 使用了类型化数组,发送的是类型化数组(uInt8Array)的buffer属性,也就是ArrayBuffer对象。 xhr.send(uInt8Array.buffer); }
blob
一个 Blob 对象表示一个不可变的,原始数据的类似文件对象。是存放二进制数据的容器。但是是类似文件对象的二进制数据。二进制数组不一定是一个文件对象。
blob实例有两个属性,size和type,size以字节为单位,type如image/jpeg
File 对象是特殊类型的 Blob
blob的来源:
通过new Blob(dataArr:Array, options:{type:string})
创建的对象就是Blob对象。dataArray可以是二进制数组,blob,字符串,type
是数组内容的MIME类型,还有slice方法
XMLHttpRequest里,如果指定responseType为blob,那么得到的返回值也是一个blob对象
canvas对象上的toBlob方法
var s = '<div>1</div>' var blob = new Blob([s],{type:'text/html'}) var abf = new ArrayBuffer(8) var blob = new Blob([abf]) var abv = new Unit8Array(abf) var blob = new Blob([abv]) // slice var blob2 = blob.slice(0,3,{type:'text/plain'}) // canvas var canvas = document.querySelector('canvas') canvas.toBlob(function(blob){ var blob3 = blob })
file
继承blob。 file的来源:
可以是来自用户在一个input元素上选择文件后返回的FileList对象
可以是来自拖放操作生成的 DataTransfer对象
可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果
实践:上传图片预览的功能
<div id="containner"> <input type="file" id="imgFile" /><br /> <img id="previewFile" src="" height="200" alt="Image preview..." /> </div> <script> const imgFile = document.querySelector("#imgFile"); const previewFile = document.querySelector("#previewFile"); imgFile.onchange = function(e) { // 类数组 file继承blob,每个file有很多属性 有继承blob的type和size,还有name,lastModified(13为时间戳) let files = e.target.files; console.log(files); // previewImageByBlob(files[0],previewFile) previewImageByBase64(files[0], previewFile); }; function previewImageByBlob(blob, imgSelector) { // create必须后面是blob对象 const imgUrl = window.URL.createObjectURL(blob); // src可以是根据blob创建的url 长得像这样blob:域名/e61c67e3-df3a-453a-8f41-df740c1f5faf imgSelector.src = imgUrl; console.log(imgUrl); // 浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。 imgSelector.onload = function() { window.URL.revokeObjectURL(imgUrl); }; } function previewImageByBase64(blob, imgSelector) { var reader = new FileReader(); // readAsArrayBuffer readAsBinaryString readAsText 好几种类型 // 读取操作完成的时候,readyState 会变成已完成(DONE),并触发 loadend 事件,同时 result 属性将包含一个data:URL格式的字符串(base64编码)以表示所读取文件的内容,data:image/jpeg;base64,/9j/4AAQ... // 如果是服务器过来的blob,需要new Blob(blob)这样继续下面的操作 var x = reader.readAsDataURL(blob); console.log(x); // 必须监听load事件,完事了才能有完整的base64 reader.onloaded = () => { console.log("result", reader.result); imgSelector.src = reader.result; }; reader.onerror = () => { console.log("出错了"); }; } </script>
Blob URL和Data URL有什么区别呢?
blob显示的形式blob:域名/e61c67e3-df3a-453a-8f41-df740c1f5faf
,dataURL的显示形式data:image/jpeg;base64,/9j/4AAQ...
Blob URL的长度一般比较短,但Data URL因为直接存储图片base64编码后的数据,往往很长,如上图所示,浏览器在显示Data URL时使用了省略号(…)。当显式大图片时,使用Blob URL能获取更好的可能性。
Blob URL可以方便的使用XMLHttpRequest获取源数据(xhr.responseType = 'blob')。对于Data URL,并不是所有浏览器都支持通过XMLHttpRequest获取源数据的
Blob URL 只能在当前应用内部使用,把Blob URL复制到浏览器的地址栏中,是无法获取数据的。Data URL相比之下,就有很好的移植性,你可以在任意浏览器中使用。
Blob URL除了可以用作图片资源的网络地址,Blob URL也可以用作其他资源的网络地址,例如html文件、json文件等,为了保证浏览器能正确的解析Blob URL返回的文件类型,需要在创建Blob对象时指定相应的type
区分escape、encodeURI和encodeURIComponent
编码之后的效果是%XX
或者%uXXXX
这种形式
escape
处理字符串,ASCII字母、数字、@*/+
这几个字符不会被编码,其余的都会
encodeURI
和encodeURIComponent
处理编码URL,类似于https://juejin.im/editor/drafts/5cde6dae6fb9a07eda02e5f1
encodeURI
方法不会对下列字符编码 ,ASCII字母、数字、~!@#$&*()=:/,;?+'
,重点是/ ? # &
encodeURIComponent
方法不会对下列字符编码 ASCII字母、数字、~!*()'
相应的解码unsecape encodeURI encodeURIComponent
综合使用,分片上传
感觉不是一时半会的,待看。重点是blob.slice
有空看这篇文章
formdata
FormData对象的作用就类似于serialize({a:1,b:2}=>a=1&b=2
)方法,不过FormData是浏览器原生的,且支持二进制文件,是个一眼就会让人喜欢的很赞的东西!
// 前端 var myFormData = new FormData(form容器); // 后端 可以使用multiparty来解析form-data的数据 不然原生 的那种会有很多别的文本,难以解析 app.post('/login',(req,res)=>{ var form = new multiparty.Form(); form.parse(req, (err, fields, files)=>{ res.send(fields.username[0]) }); })
FormData对象还有一个方法,为append()
方法,可以人为的给当前FormData对象添加一个键/值对。
myFormData.append(DOMString 键, Blob 值, [可选] DOMString 文件名); myFormData.append(DOMString 键, DOMString 值); // 示例 blob一般不写的话 myFormData.append('token','2h22h2j')
down属性formData、file,arrayBuffer数据类型arrayBuffer预览图片详细的blob大文件上传二进制文件流实现前端下载简单明了区分escape、encodeURI和encodeURIComponent