VUE框架下实现头像文件的截取和上传
程序员文章站
2022-03-08 15:58:41
...
头像截取的需求
关于用户头像的选取有时候会遇到很多问题,比如图片的大小、比例等,有时候我们不免会想:如果用户上传的每一张头像都是固定的长和换、大小也不会有太大的区别就好了,很多网站、应用也都是这么做的。我们需要的就是一个固定大小的截取框,用来截取图片并上传就好了。其实实现这个需求也不算很难,昨天写了一个VUE框架下的demo,在这里分享出来~ 这个demo里用到的插件只有element-ui
实现思路
采用css的定位属性配合Canvas就足够实现这样的功能,我们需要两个嵌套在一起的div,外层用来显示画布,内层用来充当截取框,宽高、形状属性都可以设置为你所需要的头像的属性以达到一个预览效果,采用absolute定位,保证内部div框对外部div框的位置能够得到记录,在此基础上,在外部div里内嵌一个Canvas,同样采用absolute定位在左上角,此时,内部div相对外部div的位移同样也是它相对于同级Canvas的位移。我们把图绘制在这个Canvas上,然后设置一个缩放条能够缩放图片,截取的时候在内部div里新建一个和它等大的Canvas,再依照位置偏移把同一张图绘制在内部canvas里转化为base64,就可以直接上传给后端,或者显示到头像框里。
代码
<template>
<div>
<div class="avatar">
<el-upload class="avatar-uploader" :before-upload="beforeAvatarUpload" action="#">
<img v-if="showImg" :src="dataUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
<div id="editBox">
<canvas id="canvas"></canvas>
<div id = "cutter" :style="getCutterPos()" @mousedown=drag($event)>
<canvas id="inner"></canvas>
</div>
<div class="block">
<span class="demonstration">缩放:</span>
<el-slider class="slider" v-model="value" :min="minValue" :max="maxValue" :disabled="!hasImg"></el-slider>
</div>
<div class="ok" @click="showImage">确定</div>
</div>
</div>
</template>
<script>
export default {
name: "testImage",
mounted(){
this.initCanvas();
},
props:{
},
data() {
return {
minValue: 0,//缩放条的最小值,依据传入图片改动
value: 0,//缩放条的值
maxValue: 200,//缩放条的最大值,依据传入图片改动
imageUrl: '',//所选图片的路径
commitable: false,
img: null,//图片文件
hasImg:false,//画布上是否有图
canvas: null,//外部canvas
context: null,//外部canvas的ctx
showImg: false,
X: 0,//截取框位移
Y: 0,//截取框位移
type: '',//文件类型
dataUrl: ''//base64
};
},
watch:{
value(val,oldVal){//动态缩放图片
this.context.fillRect(0,0,750,450);
var scale = val/100;
this.context.drawImage(this.img,0,0,this.img.width*scale,this.img.height*scale);
}
},
methods: {
drag(ev){//截取框拖动,判定边界不超过外部canvas(750*450)
var event = ev || event;
var startX = event.clientX
var startY = event.clientY
var _that = this
var PX = _that.X;
var PY = _that.Y;
document.onmousemove = function(ev){
var event = ev || event;
if(PX+event.clientX-startX>=0&&PX+event.clientX-startX<=600){
_that.X = PX+event.clientX-startX;
}
if(PY+event.clientY-startY>=0&&PY+event.clientY-startY<=300){
_that.Y = PY+event.clientY-startY;
}
}
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
}
return false;
},
getCutterPos(){
return "top:"+(this.Y-2)+"px;left:"+(this.X-2)+"px;"
},
initCanvas(){//初始化外部canvas
this.canvas = document.getElementById("canvas")
this.canvas.height = 450;
this.canvas.width = 750;
this.context = this.canvas.getContext("2d");
this.context.fillStyle = "#FFF";
this.context.fillRect(0,0,750,450);
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg'||file.type === 'image/png';//判定格式
this.type = file.type;
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 或 PNG 格式!');
}
this.imageUrl = URL.createObjectURL(file)//创建图片链接
this.startCanvas();
},
startCanvas(){//外部canvas开始工作
this.hasImg = false;
this.context.fillRect(0,0,750,450);
if(this.imageUrl=='') return;
this.img = new Image();
this.img.src = this.imageUrl;
var _that = this
var canvas = _that.canvas
var img = this.img
this.img.onload = function(){
_that.hasImg = true;
var Max_height = canvas.height;//不论上传的图片多大,保证图片被包含在画布内,以此为依据更改缩放条的最大值
var Max_width = canvas.width;
_that.maxValue = (Max_width/img.width<Max_height/img.height?Max_width/img.width:Max_height/img.height)*100;
_that.value = 100<_that.maxValue?100:_that.maxValue;
var scale = _that.value/100;
_that.context.drawImage(img,0,0,img.width*scale,img.height*scale);
}
},
showImage(){
this.hasImg = false;
var canvas = document.getElementById("inner")
canvas.height = 150;//内部canvas和截取框等大
canvas.width = 150;
var context = canvas.getContext("2d");
var img = new Image();
img.src = this.imageUrl;
var _that = this
img.onload = function(){
var scale = _that.value/100;
context.drawImage(img,-_that.X,-_that.Y,img.width*scale,img.height*scale);//实现截图
_that.dataUrl = canvas.toDataURL(_that.type);//获取base64
_that.showImg = true;//显示图片
_that.hasImg = true;
canvas.height = 0;
canvas.width = 0;
}
}
}
};//CSS一定要采用绝对定位!!!
</script>
<style scoped>
#canvas{
position: absolute;
left: 0px;
top: 0px;
}
#editBox{
border: skyblue solid 20px;
background-color: skyblue;
position: absolute;
left: 400px;
top: 100px;
height: 500px;
width: 750px;
}
.demonstration{
position: absolute;
bottom: 10px;
left: 10px;
}
.slider{
position: absolute;
left: 70px;
bottom: 0px;
width: 550px;
}
.block{
position: absolute;
bottom: 0px;
width: 700px;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
border-radius: 50%;
}
.avatar {
border: 1px solid black;
width: 178px;
height: 178px;
display: block;
border-radius: 50%;
}
.ok{
cursor: pointer;
position: absolute;
right: 5px;
bottom:-2px;
background-color: skyblue;
border-radius: 10px;
border: 2px solid white;
height: 40px;
width: 90px;
font-size: 20px;
line-height: 40px;
color: white;
}
#cutter{
position: absolute;
border: 2px solid red;
height: 150px;
width: 150px;
border-radius: 50%;
}
#inner{
position: absolute;
left: 0px;
top: 0px;
}
</style>
Demo展示和后话
上传的时候直接给后端发去base64即可,回传数据可以和后端协商。