vue 拍照上传 涉及技术点 canvas裁剪 base64 blob数据装换 forData 数据传递 ,还有hammer.js和exif-js具体代码如下
<template>
<div class="pic">
<div class="bg-box">
<div class="img-box">
<img src="../../assets/img/news_bg.png" width="100%" alt="">
</div>
<div class="headers">
<van-nav-bar title="个人信息" @click-left="onClickLeft" >
<img class="img" src="../..//assets/img/back@2x.png" slot="left" alt="">
</van-nav-bar>
</div>
</div>
<form enctype="multipart/form-data" >
<div class="uploadBox">
<!--主编辑器canvas-->
<canvas id="canvas" :width="windowWidth" :height="windowWidth"></canvas>
<!--蒙层canvas-->
<canvas id="canvasMask" :width="windowWidth" :height="windowWidth"></canvas>
<!--用于导出图片的canvas-->
<canvas id="resultImg" width="160" height="160" style="display: none"></canvas>
<label class="uploadBar" for="uploadInput" v-if="uploadBarShow">
<input type="file"
style="display: none"
id="uploadInput"
accept="image/*"
ref="file"
@change="upLoadChange">
<span>点击上传</span>
</label>
</div>
<!-- <img :src="src" v-if="src"/> -->
<div class="select-box" v-if="!uploadBarShow">
<!-- <input type="submit" class="sure-bt" @click="crop" value="确定裁切"> -->
<div class="sure-bt" @click="crop" >确定裁切</div>
<div class="cancel-bt" @click="cancel">取消</div>
</div>
</form>
</div>
</template>
<script>
import {Toast} from "vant";
import axios from "axios";
import { dataURItoBlob } from "../../assets/js/blob.js"
import { mapMutations } from "vuex";
require("@/assets/plugin/hammer.min"); //引入hammerJS
import qs from "qs"
import EXIF from "exif-js";
export default {
name: "home",
data() {
return {
src: "",
orientation: null, //图片元信息
degree: 0, //原图片旋转角度
windowWidth: 0, //屏幕的宽度
imgEl: null,
imgWidth: 0, //图片的宽度
imgHeight: 0, //图片的高度
transWidth: 0, //改变后的图片宽度
transHeight: 0, //改变后的图片高度
initScale: 0,
transformScale: 1, //初始缩放
prevX: 0, //上一次的X轴
prevY: 0, //上一次的Y轴
translateX: 0, //平移X轴
translateY: 0, //平移Y轴
uploadBarShow: true, //上传按钮显示
canvas: null, //canvas
canvasMask: null, //canvas-mask
canvasResult: null, //裁切图片canvas
eq_types : "iOS"
};
},
methods: {
...mapMutations([
'header_pic_update'
]),
onClickLeft() {
this.$router.go(-1);
},
cancel() {
this.$router.go(-1);
},
/**
* 上传图片
* */
upLoadChange() {
let files = this.$refs.file.files[0],
self = this;
let val = this.$refs.file.value;
// console.log("文件是:",files);
// console.log("文件是:",val);
/*控制图片上传大小不超过1MB*/
if (files.size > 8388608) {
alert("图片不能超过1MB大小");
return false;
}
/*用EXIF获取图片元信息*/
EXIF.getData(files, function() {
self.orientation = EXIF.getTag(this, "Orientation");
});
let fr = new FileReader();
// 监听reader对象的的onload事件,当图片加载完成时,把base64编码賦值给预览图片
fr.addEventListener(
"load",
() => {
if (this.orientation) {
/*需要对ios做一下兼容*/
this.getImgData(fr.result, this.orientation, data => {
this.createCanvas(data); //初始化canvas
});
} else {
this.createCanvas(fr.result); //初始化canvas
}
},
false
);
fr.readAsDataURL(files);
},
/**
* 初始化canvas
* */
createCanvas(imgBase64) {
this.imgEl = new Image();
this.imgEl.src = imgBase64;
this.imgEl.onload = () => {
this.imgWidth = this.imgEl.width; //初始化图片的宽
this.imgHeight = this.imgEl.height; //初始化图片的高
// 画蒙层
this.canvasMask.globalCompositeOperation = "source-out";
this.canvasMask.fillStyle = "rgb(255,255,255)";
this.canvasMask.arc(
this.windowWidth / 2,
this.windowWidth / 2,
80,
0,
2 * Math.PI
);
this.canvasMask.fill();
this.canvasMask.fillStyle = "rgba(0,0,0,0.7)";
this.canvasMask.fillRect(0, 0, this.windowWidth, this.windowWidth);
// 当图片比canvas小时不做任何改变
if (
this.imgEl.width < this.windowWidth &&
this.imgEl.height < this.windowWidth
) {
this.imgWidth = this.imgEl.width;
this.imgHeight = this.imgEl.height;
} else {
//原图片宽高比例 大于 图片框宽高比例
if (1 <= this.imgEl.width / this.imgEl.height) {
this.imgWidth = this.windowWidth; //以框的宽度为标准
this.imgHeight =
this.windowWidth * (this.imgEl.height / this.imgEl.width);
} else {
//原图片宽高比例 小于 图片框宽高比例
this.imgWidth =
this.windowWidth * (this.imgEl.width / this.imgEl.height);
this.imgHeight = this.windowWidth; //以框的高度为标准
}
}
this.canvas.translate(this.windowWidth / 2, this.windowWidth / 2); //把canvas原点移动到中心位置
this.canvas.drawImage(
this.imgEl,
0 - this.imgWidth / 2,
0 - this.imgHeight / 2,
this.imgWidth,
this.imgHeight
);
/*初始化hammer*/
this.initHammer();
};
},
/**
* @param {string} img 图片的base64
* @param {int} dir exif获取的方向信息
* @param {function} next 回调方法,返回校正方向后的base64
* */
getImgData(img, dir, next) {
let image = new Image();
image.src = img;
image.onload = function() {
let degree = 0,
drawWidth,
drawHeight,
width,
height;
drawWidth = image.naturalWidth; //暂存图片的宽
drawHeight = image.naturalHeight; //暂存图片的高
//以下改变一下图片大小
let maxSide = Math.max(drawWidth, drawHeight);
console.log(maxSide);
if (maxSide > 4032) {
let minSide = Math.min(drawWidth, drawHeight);
minSide = minSide / maxSide * 4032;
maxSide = 4032;
if (drawWidth > drawHeight) {
drawWidth = maxSide;
drawHeight = minSide;
} else {
drawWidth = minSide;
drawHeight = maxSide;
}
}
let canvas = document.createElement("canvas");
canvas.width = width = drawWidth;
canvas.height = height = drawHeight;
let context = canvas.getContext("2d");
//判断图片方向,重置canvas大小,确定旋转角度,iphone默认的是home键在右方的横屏拍摄方式
switch (dir) {
//iphone横屏拍摄,此时home键在左侧
case 3:
degree = 180;
drawWidth = -width;
drawHeight = -height;
break;
//iphone竖屏拍摄,此时home键在下方(正常拿手机的方向)
case 6:
canvas.width = height;
canvas.height = width;
degree = 90;
drawWidth = width;
drawHeight = -height;
break;
//iphone竖屏拍摄,此时home键在上方
case 8:
canvas.width = height;
canvas.height = width;
degree = 270;
drawWidth = -width;
drawHeight = height;
break;
}
//使用canvas旋转校正
context.rotate(degree * Math.PI / 180);
context.drawImage(this, 0, 0, drawWidth, drawHeight);
//返回校正图片
next(canvas.toDataURL("image/jpeg"));
};
},
/**
* 初始化hammer
* */
initHammer() {
//隐藏上传bar
this.uploadBarShow = false;
let hammer = new Hammer(document.querySelector("#canvasMask"));
hammer.get("pinch").set({ enable: true });
hammer.get("rotate").set({ enable: true });
/*缩放 */
hammer.on("pinchmove pinchstart pinchin pinchout", e => {
if (e.type === "pinchstart") {
this.initScale = this.transformScale || 1;
}
this.transformScale = this.initScale * e.scale;
this.canvas.clearRect(
0 - this.windowWidth / 2,
0 - this.windowWidth / 2,
this.windowWidth,
this.windowWidth
);
this.transWidth = this.imgWidth * this.transformScale;
this.transHeight = this.imgHeight * this.transformScale;
this.canvas.drawImage(
this.imgEl,
this.translateX - this.transWidth / 2,
this.translateY - this.transHeight / 2,
this.transWidth,
this.transHeight
);
});
/*平移*/
hammer.on("panstart panmove", e => {
if (e.type === "panstart") {
this.prevX = this.translateX;
this.prevY = this.translateY;
}
this.translateX = this.prevX + e.deltaX;
this.translateY = this.prevY + e.deltaY;
/*擦除canvas*/
this.canvas.clearRect(
0 - this.windowWidth / 2,
0 - this.windowWidth / 2,
this.windowWidth,
this.windowWidth
);
this.canvas.drawImage(
this.imgEl,
this.translateX - (this.transWidth || this.imgWidth) / 2,
this.translateY - (this.transHeight || this.imgHeight) / 2,
this.transWidth || this.imgWidth,
this.transHeight || this.imgHeight
);
});
},
/**
* 裁切
* */
crop() {
let base64 = document.querySelector("#canvas").toDataURL("image/png");
let nImg = new Image();
nImg.src = base64;
nImg.onload = () => {
this.canvasResult.fillStyle = "white";
this.canvasResult.fillRect(0, 0, 160, 160);
this.canvasResult.drawImage(
nImg,
-(this.windowWidth / 2 - 80),
-(this.windowWidth / 2 - 80)
);
/*最后导出裁切好的图片为base64码*/
//**将base64传给后台 */
this.src = document.querySelector("#resultImg").toDataURL("image/jpeg");
// 将base64转成blob
let upload_imgs = dataURItoBlob(this.src)
console.log(upload_imgs, "base64:-->"+this.src);
let formData = new FormData();
formData.append('action', "edit_user_pic");
formData.append('files', upload_imgs, 'image.jpeg');
//有些苹果不支持get方法
// console.log(formData);
// console.log(formData.get('action'));
// console.log(formData.get('files'));
//添加请求头
axios({
headers: {
'Content-Type':'multipart/form-data'
},
method: 'post',
url: '/api/fileApi',
data: formData
})
.then(res =>{
console.log(res);
if(res._code !== "99999"){
Toast({
message : res._msg,
duration : 2500
});
return;
};
this.header_pic_update(res._result.userPic);
this.$router.go(-1);
})
};
}
},
mounted() {
let canvas = document.querySelector("#canvas"),
canvasMask = document.querySelector("#canvasMask"),
canvasRsut = document.querySelector("#resultImg");
this.canvas = canvas.getContext("2d");
this.canvasMask = canvasMask.getContext("2d");
this.canvasResult = canvasRsut.getContext("2d");
this.windowWidth = window.innerWidth;
},
created(){
let u = navigator.userAgent;
if (u.indexOf("Android") > -1 || u.indexOf("Linux") > -1) {
//安卓手机
this.eq_types = "android";
} else if (u.indexOf("iPhone") > -1) {
//苹果手机
this.eq_types = "iOS";
} else if (u.indexOf("Windows Phone") > -1) {
//winphone手机
}
}
};
</script>
<style lang="less" scoped>
.pic {
position: relative;
height: 100vh;
.bg-box {
position: relative;
height: 139px;
.img-box {
position: absolute;
top: 0;
left: 0;
z-index: -5;
width: 100%;
}
.headers {
padding-top: 57px;
margin: 0 44px;
/deep/ .van-hairline--bottom {
background: transparent;
color: #fff;
height: 55px;
line-height: 55px;
}
/deep/ .van-nav-bar__left {
top: 0;
}
/deep/ .van-nav-bar__title {
font-size: 36px;
}
/deep/ .van-hairline--bottom::after {
border: none;
}
.img {
width: 55px;
height: 55px;
}
}
}
}
* {
padding: 0;
margin: 0;
}
.uploadBox {
position: relative;
}
#canvasMask {
position: absolute;
left: 0;
top: 0;
z-index: 5;
}
.uploadBar {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 20vw;
height: 20vw;
border: 1px dashed gray;
text-align: center;
line-height: 20vw;
font-size: 15px;
color: gray;
display: block;
z-index: 10;
}
.select-box {
height: 100px;
margin: 20px 30px;
display: flex;
justify-content: space-between;
align-items: center;
color: #fff;
font-size: 36px;
.sure-bt {
width: 200px;
height: 80px;
line-height: 80px;
border-radius: 10px;
background: #44bb00;
}
.cancel-bt {
width: 200px;
height: 80px;
line-height: 80px;
border-radius: 10px;
background: #ff976a;
}
}
</style>
blob代码如下
export const dataURItoBlob = function (base64Data) {
var byteString;
if (base64Data.split(',')[0].indexOf('base64') >= 0)
byteString = atob(base64Data.split(',')[1]);
else
// byteString = unescape(base64Data.split(',')[1]);
byteString = decodeURI(base64Data.split(',')[1]);
var mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type: mimeString});
}
这样就可以实现拍照上传:
注意:
在验证数据是否append到fordata中,用formData.get(‘action’),部分ios不兼容这个方法要注释掉
如果是放在自己app种webView中安卓app需要对其做支持否则input可能无法触发,H5浏览器则完全适用