canvas和base64
base64是二进制数据的一个编码格式,就像utf8一样的东西
他跟json一样,也是前后端交互能够相互识别的数据,他更多的是用来传递文件数据
在js里生成base64的API有两个,一个是FileReader
,一个是画布canvas
FileReader
<input type="file" onchange="change(this.files[0])">
function change(file){ var fr = new FileReader() fr.onload = function(e) { // 这个就是base64 console.log( e.target.result ); } // 这个方法传参是一个Blob类型的格式 fr.readAsDataURL(file) }
canvas
我们使用画布是为了获取画布上的内容
画布的输入是图片,然后对这个图片进行剪切,打水印什么的
画布上的内容的输出格式是base64
// 这个image就是输入 // 除了new,也可以直接取页面上的标签 var image = new Image(); image.onload = function () { var w = image.width; var h = image.height; var canvas = document.createElement('canvas'); var ctx = canvas.getContext("2d"); canvas.width = w; canvas.height = h; ctx.drawImage(image, 0, 0, w, h); // 可以在这里添加水印或者合并图片什么的 ... // 把画布的内容转成base64,这个就是输出 var base64 = canvas.toDataURL('image/jpeg'); console.log(base64) } // 这个src可以是图片地址,也可以是上面fileReader的base64 image.src = "xxx.jpg";
如果画布想要压缩图片
// 压缩比例 var bili = 0.7; var base64 = canvas.toDataURL('image/jpeg',bili);
画布的兼容
画布的api是非常不友好的
- 跨域问题(往下看有解决方案)
- ios系统的画布小得过分(所以有些需求要做网页版的ps的千万别接)
- 拍照还会旋转(往下看有解决方案)
关于画布跨域
张大神的跨域的解决方案
// 不管画布的输入,即图片的来源是new Image() 还是 document.querySelector() // 如果图片是外链,产生了跨域,那画布会报错 // Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported. // Tainted canvases 【被污染的画布】 // 解决方案是先让后端开启图片支持跨域CORS // 然后在new Image() 或者 document.querySelector() 之后加上 img.crossOrigin=""
画布太小,不能获得base64
解决方案是不通过画布获得base64,可以通过请求图片地址,修改响应头获得blob
格式文件,然后让fileReader把blob转成base64,缺点是请求不允许跨域,上代码
function getBase64(imgUrl) { window.URL = window.URL || window.webkitURL; var xhr = new XMLHttpRequest(); xhr.open("get", imgUrl, true); // 至关重要 xhr.responseType = "blob"; xhr.onload = function () { if (this.status == 200) { //得到一个blob对象 var blob = this.response; console.log("blob", blob) // 至关重要 let fr = new FileReader(); fr.onloadend = function (e) { let base64 = e.target.result; console.log(base64) }; fr.readAsDataURL(blob); var img = document.createElement("img"); img.onload = function (e) { window.URL.revokeObjectURL(img.src); // 清除释放 }; let src = window.URL.createObjectURL(blob); console.log(src) img.src = src; document.getElementById("xxx").appendChild(img); } } xhr.send(); } getBase64("xx.png")
关于blob
上面使用到了blob的知识
从input onchange中返回的图片对象其实就是一个File对象。
而Blob对象是一个用来包装二进制文件的容器,File继承于Blob。
FileReader是用来读取内存中的文件的API,支持File和Blob两种格式。
FileReader和URL.createObjectURL的区别
资料来自掘金网友
区别一
- 通过FileReader.readAsDataURL(file)可以获取一段data:base64的字符串
- 通过URL.createObjectURL(blob)可以获取当前文件的一个内存URL
区别二
- createObjectURL是同步执行(立即的)
- FileReader.readAsDataURL是异步执行(过一段时间)
区别三
- createObjectURL返回一段带hash的url,并且一直存储在内存中,直到document触发了unload事件(例如:document close)或者执行revokeObjectURL来释放。
- FileReader.readAsDataURL则返回包含很多字符的base64,并会比blob url消耗更多内存,但是在不用的时候会自动从内存中清除(通过垃圾回收机制)
区别三
- createObjectURL支持从IE10往上的所有现代浏览器
- FileReader.readAsDataURL同样支持从IE10往上的所有现代浏览器
拍照旋转的原因和解决方案
资料来自掘金网友
为什么从相机拍照获取的图片会旋转呢?
是因为从相机拍照获取的图片的EXIF(Exchangeable image file format)会默认设置一个orientation tag
目前只有jpeg格式的图片会有
解决方案有
用EXIF.js插件获取图片的orientation
进行判断
简易版EXIF.js用DataView
API 代码来自stackoverflow
function getOrientation(file, callback) { var reader = new window.FileReader(); reader.onload = function (e) { var view = new window.DataView(e.target.result); if (view.getUint16(0, false) != 0xFFD8) { return callback(-2); } var length = view.byteLength, offset = 2; while (offset < length) { var marker = view.getUint16(offset, false); offset += 2; if (marker == 0xFFE1) { if (view.getUint32(offset += 2, false) != 0x45786966) { return callback(-1); } var little = view.getUint16(offset += 6, false) == 0x4949; offset += view.getUint32(offset + 4, little); var tags = view.getUint16(offset, little); offset += 2; for (var i = 0; i < tags; i++) { if (view.getUint16(offset + (i * 12), little) == 0x0112) { return callback(view.getUint16(offset + (i * 12) + 8, little)); } } } else if ((marker & 0xFF00) != 0xFF00) { break; } else { offset += view.getUint16(offset, false); } } return callback(-1); }; reader.readAsArrayBuffer(file); } // 将图片旋转到正确的角度 function resetOrientation(srcBase64, srcOrientation, callback) { var img = new Image(); img.onload = function() { var width = img.width, height = img.height, canvas = document.createElement('canvas'), ctx = canvas.getContext("2d"); // set proper canvas dimensions before transform & export if ([5,6,7,8].indexOf(srcOrientation) > -1) { canvas.width = height; canvas.height = width; } else { canvas.width = width; canvas.height = height; } // transform context before drawing image // -2: not jpeg // -1: not defined switch (srcOrientation) { case 2: ctx.transform(-1, 0, 0, 1, width, 0); break; case 3: ctx.transform(-1, 0, 0, -1, width, height ); break; case 4: ctx.transform(1, 0, 0, -1, 0, height ); break; case 5: ctx.transform(0, 1, 1, 0, 0, 0); break; case 6: ctx.transform(0, 1, -1, 0, height , 0); break; case 7: ctx.transform(0, -1, -1, 0, height , width); break; case 8: ctx.transform(0, -1, 1, 0, 0, width); break; default: ctx.transform(1, 0, 0, 1, 0, 0); } // draw image ctx.drawImage(img, 0, 0); // export base64 callback(canvas.toDataURL('image/jpeg')); }; img.src = srcBase64; };
然后用画布旋转正确后在生成正确的base64,问题是画布兼容性也很不友好啊
如果有关于手机画布的需要能不做尽量不做,pc端可以做一做,微信的相册自带的画布是手机端最好使的,微信把手机的兼容都大概搞定了,这也是为什么说再别人的平台上做网页好的地方,API众多,兼容性也很好,微信的代码查看《微信公众号》篇