H5获取相册图片进行编辑裁剪上传
kekobin opened this issue · 0 comments
基础知识
dataURL
一个完整的 dataURI 应该是这样的:
data:[<mediatype>][;base64],<data>
其中mediatype声明了文件类型,遵循MIME规则,如“image/png”、“text/plain”;之后是编码类型,这里我们只涉及 base64;紧接着就是文件编码后的内容了。我们常常在 HTML 里看到img标签的src会这样写:
src="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7"
这个img引用的就是以 base64 编码的 dataURL 了,只要浏览器支持,就可以被解码成声明格式的图片并渲染出来。
.toDataURL()
canvas.toDataURL([type, encoderOptions]);
- type: 图片格式,默认为 image/png
- encoderOptions: 在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。(这可以用于图片压缩,即指定质量为<1的就可以将原图片压缩到一定的比例)
图片加载是异步的,在转换成 dataURL 前必须先确保图片成功加载到,否则让 canvas 即刻执行绘制可能失败,从而导致转换 dataURL 失败。于是.toDataURL()方法应该写在的onload事件中,以确保 canvas 的绘制工作在图片下载完成后开始。好在.drawImage()方法是同步的,只有在 canvas 绘制完成后才会执行后续操作,比如.toDataURL()。
function getBase64(url){
//通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片,相比 createElement() 创建 <img> 省去了 append(),也就避免了文档冗余和污染
var Img = new Image(),
dataURL='';
Img.src=url;
Img.onload=function(){ //要先确保图片完整获取到,这是个异步事件
var canvas = document.createElement("canvas"), //创建canvas元素
width=Img.width, //确保canvas的尺寸和图片一样
height=Img.height;
canvas.width=width;
canvas.height=height;
canvas.getContext("2d").drawImage(Img,0,0,width,height); //将图片绘制到canvas中
dataURL=canvas.toDataURL('image/jpeg'); //转换图片为dataURL
};
}
调取手机相册和相机
使用的是类似input[type=file]文件上传的形式,只不过在手机端会加上一些配置,从而能默认识别到相册 :
<form id="uploadForm" enctype="multipart/form-data">
<input class="upload-open-photo" accept="image/*" type="file" id="filechooser" @change="btnUploadFile($event)"/>
</form>
form-enctype: 设置或返回表单用来编码内容的 MIME 类型,默认值是"application/x-www-form-urlencoded",当 input type 是 "file" 时,值是 "multipart/form-data"。
处理上传的图片为dataURL
btnUploadFile(e) {
// 获取图片
const { files } = e.target;
const file = files[0];
const self = this;
// 接受 jpeg, jpg, png 类型的图片
if (!/\/(?:jpeg|jpg|png)/i.test(file.type)) {
return;
}
const reader = new FileReader();
reader.onload = function onload() {
// 经过reader.readAsDataURL读取,这里的result为base64格式
const { result } = this;
let img = new Image();
// 进行图片的渲染
img.onload = () => {
// 对图片进行压缩,返回的是压缩后的图片的base64
const ret = self.compress(img, file.type);
self.filename = file.name;
// self.imgSrc = ret;
// 对压缩后的图片进行裁剪
self.initCroppie(ret);
img = null;
};
img.src = result;
};
// 将file读取成dataURL格式
reader.readAsDataURL(file);
}
对读取成dataURL的图片进行压缩
压缩处理步骤:
- 利用 canvas 的 drawImage 将目标图片画到画布上
- 利用画布调整绘制尺寸,以及导出的 quality ,确定压缩的程度
- 利用 canvas的 toDataURL 或者 toBlob 可以将画布上的内容导出成 base64 格式的数据。
compress(img, fileType) {
const drawWidth = img.width;
const drawHeight = img.height;
let canvas = document.createElement('canvas');
canvas.width = drawWidth;
canvas.height = drawHeight;
let context = canvas.getContext('2d');
// 先画到画布上
context.drawImage(img, 0, 0, drawWidth, drawHeight);
// 然后利用画布的功能进行压缩,压缩0.5就是压缩百分之50
const base64data = canvas.toDataURL(fileType, 0.5);
canvas = null;
context = null;
return base64data;
}
如果要调整图片方向的,可使用canvas的rotate:
const degree = 180;
drawWidth = -drawWidth;
drawHeight = -drawHeight;
context.rotate(degree * Math.PI / 180);
对压缩后的图片进行裁剪
使用的是Croppie 这个库实现放缩、移动、裁剪功能,但是其只支持相对或者绝对地址的图片url,所以先需要把我们的dataURI格式图片上传到服务器生成url。
dataURI转换成blob
由于上传接口只支持blob(file)格式,所以需要先把dataURI转换成blob:
// dataURI is base64 format
dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
let byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0) {
// 解码base64成为 binary string
byteString = atob(dataURI.split(',')[1]);
} else {
byteString = unescape(dataURI.split(',')[1]);
}
// separate out the mime component
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// binary string没法转成blob的, 所以先转成ArrayBuffer形式,这里使用
//Uint8Array形式,将binary string字符串存进去,然后就可以使用 new Blob转换了
// write the bytes of the string to a typed array
const ia = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i += 1) {
ia[i] = byteString.charCodeAt(i);
}
// Blob有兼容性问题,需要兼容处理
let blob;
try {
blob = new Blob([ia], { type: mimeString });
} catch (error) {
const BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
const bb = new BlobBuilder();
bb.append([ia]);
blob = bb.getBlob(mimeString);
}
return blob;
}
上传blob
async upload(base64) {
const blob = this.dataURItoBlob(base64);
const fd = new FormData();
fd.append('filename', blob, this.filename);
return uploadImg(fd);
}
实现裁剪
初始化
async initCroppie(base64) {
// 现将dataURI转成blob进行上传(因为上传只支持blob格式),得到上传后的图片url
// 因为裁剪使用的是Croppie,只支持相对或者绝对的图片url进行初始化
const uploadRet = await this.upload(base64);
const el = document.getElementById('croppie-container');
this.croppieTarget = new window.Croppie(el, {
viewport: { width: 100, height: 100 },
boundary: { width: 300, height: 300 },
showZoomer: false,
enableOrientation: false // 这里注意,填false,否则默认图片会反过来
});
this.croppieTarget.bind({
url: uploadRet.data, // 这里是图片的url
orientation: 4
});
}
裁剪可通过一个按钮触发
croppie() {
this.croppieTarget.result('blob').then(async (blob) => {
// do something with cropped blob
console.log(blob);
const fd = new FormData();
// 注意,这里要把filename放在第三个参数带进去,因为blob里面是没有的
fd.append('filename', blob, this.filename);
const res = await uploadImg(fd);
this.imgSrc = res.data;
});
}
参考
利用 canvas 压缩图片
移动端 h5 照片的处理
HTML5调用照相机并自定义显示获取到的图片
用canvas的toDataURL()将图片转为dataURL(base64)
H5拍照上传填坑