1.基于vue建设一个裁剪框
全文附录我会放到最后,有需要的小伙伴自取
## 1.1在没有添加图片的时候显示如上图的css样式
<label class="h-photo-left" v-show="!img">
<input type="file" ref="inputer" id="upload" name="upload" v-show="false" accept="image/jpg,image/png,image/jepg" @change="getFile($event)">
<div class="h-photo-left-linetop"></div>
<div class="h-photo-left-linebottem"></div>
</label>
用v-show将input标签隐藏
@change="getFile($event)"
事件由其父标签触发,即上传图片
accept="image/jpg,image/png,image/jepg"
accept接收图片格式
getFile:function (e) {
var that = this;
this.file = e.target.files[0];
if (this.file.size > 2*1024*1024) {
console.log("图片不能大于2M");
return false
}
if (this.file) {
let file = this.file
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
// base64
var imgInfo = new Image()
imgInfo.src = this.result
that.img = this.result;
// 图片加载对图片进行进行填充修改
imgInfo.onload = function () {
//这里是具体的操作
}
}
};
}else{
return false
}
},
2.分析需要实现的功能:
我此处上传的图片的大小为1920*1080的图片 而我的css设置的样式框为200 * 200
所以首先需要对图片进行自适应填充的匹配:分为两种情况:
(1):width >= height
(2):height > width
tips:可以有更为优化的版本 宽高比值就能反映很多东西,不过本文还是分情况了。
css宽高为200 * 200
(1):width >= height
宽度=200,高度=宽度 * 宽高比
(2):height >= width
高度=200,宽度=高度 * 宽高比
继续对图中所需功能进行分析
当然图中的子组件全为 absolute 定位
需要:
(1)一张图片为背景 呈灰色显示
(2)一张图片随着裁剪框移动呈高亮显示
(3)裁剪框 以及裁剪框四周的用于放大缩小裁剪框的小方块
1.第一点没什么好说的 将图片的index调低 再加个灰色遮罩就能实现
2.第二点我在此处设置了另一个和裁剪框 大小一致的显示框 通过overflow:hidden 隐藏周围不需要的图片显示区域 只留下需要显示的高亮区域
3.使用mousemove事件移动裁剪框 通过absolute定位产生的top left
以及 mousedown到mouseup过程中产生的鼠标的clienx的相对定位的距离 来移动top left 即可
同理:显示框的移动同裁剪框的top和left
4.注意:
由于我并没有在初始化图片加载过程中 让图片区域的父div称为绝对定位的父元素,而是让整个200 * 200 的黑色框 成为了 父元素 所以在处理top的过程中可能有些繁琐
当然最好还是让整个图片的显示框称为父元素,那么top left 的移动即为(0,0)点 到(x,y)点的距离
而不是(0 ,0 + 基础的top)点到 (x , y+基础的top)
所以本文中的处理也是相对麻烦 (有想尝试的小伙伴可以自己尝试)
moveStart:function (e) {
this.moveFlag = true
// 用client而不用offset的原因 优化用户体验
this.disX = e.clientX;
this.disY = e.clientY;
},
moveActive:function (e) {
if (this.moveFlag) {
// 获取鼠标移动的相对变量l t
var l = e.clientX - this.disX;
var t = e.clientY - this.disY;
// 根据获取的相对变量 以及初始位置信息在move事件中 不停地改变cripTop
var left = this.cripLeftSave
var top = this.cripTopSave
this.cripLeft = left + l
this.cripTop = top + t
this.cripTopLeftChange()
//限制
this.rcripLeft = -this.cripLeft + this.left
this.rcripTop = -this.cripTop + this.top
//右侧显示框的赋值
}
在处理 裁剪框移动时 最好先将this.cripLeft 保存到 this.cripLeftSave中去 然后进行累加
因为 l t 的数值是指 鼠标点击 到 鼠标移动到的距离 所以我们的left 和 top 也要 记录鼠标移动时的初始值 才能与其保持一致 最后在对显示框进行赋值
右侧显示框的 放大缩小原理 也十分简单
style="{'width':width*(100/rwidth) + 'px' ,'height':height*(100/rwidth) + 'px','top':rcripTop*(100/rwidth) + 'px','left':rcripLeft*(100/rwidth) + 'px'}"
其中的width heght top left 只需要在基础值上 乘以 100/裁剪框的大小即可
100位右侧显示框的宽高 除以裁剪框的宽高比 即为放大缩小的倍数
此外关于图片的上传 此功能为头像的上传 所以最好还是要裁剪图片
此处使用了canvas
<canvas class="canvas" v-show="false" id="canvas" ref="canvas" width="100" height="100"></canvas>
宽高设置为100,100 在点击更新时 将图片绘制 绘制到canvas中 形成100*100的2d图 并将该图上传即可
将其隐藏
cripPhoto:function (x,y,z) {
var canvas = this.$refs.canvas
var ctx = canvas.getContext(‘2d’)
var image = new Image()
image.src = this.img
let that = this
image.onload = function () {
// x,y 即定位
// ctx.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
ctx.clearRect(0,0,100,100);
ctx.drawImage(image,image.width*(-x),image.height*(-y),image.height*z,image.height*z,0,0,100,100)
let imageUrl = canvas.toDataURL("image/png");
that.imgUpdate.src = imageUrl
console.log(that.imgUpdate);
//上传
that.update()
}
},
附录:(未优化版,原因:上手开发,未做设置)
<template>
<div class="home-photo">
<div class="home-photo-wrap">
<div class="h-photo">
<label class="h-photo-left" v-show="!img">
<input type="file" ref="inputer" id="upload" name="upload" v-show="false" accept="image/jpg,image/png,image/jepg" @change="getFile($event)">
<div class="h-photo-left-linetop"></div>
<div class="h-photo-left-linebottem"></div>
</label>
<div class="cut-box" @mousemove="moveActive($event),shapeMoveActive($event)" @mouseup="moveEnd($event),shapeMoveEnd($event)">
<!-- 后方背景图 -->
<div class="h-photo-reget-wrap" :style="{'width':width + 'px','height':height + 'px','top':top + 'px','left':left + 'px'}">
<img class="h-photo-reget" v-show="img" :src="img" alt="" :style="{'width':width + 'px','height':height + 'px'}">
</div>
<!-- 遮罩 -->
<div class="h-photo-reget-bg" v-show="img"></div>
<!-- 前方背景图 -->
<div class="h-photo-reget-crip h-photo-reget-wrap" :style="{'width':rwidth + 'px','height':rwidth + 'px','top':cripTop + 'px','left':cripLeft + 'px'}">
<img class="h-photo-reget-crip" v-show="img" :src="img" alt="" :style="{'width':width + 'px' ,'height':height + 'px','top':rcripTop + 'px','left':rcripLeft + 'px'}">
</div>
<div class="h-photo-reget-shape" :style="{'width':rwidth + 'px','height':rwidth + 'px','top':cripTop + 'px','left':cripLeft + 'px'}" @mousedown="moveStart">
<div class="h-p-shape shpae-lt" @mousedown.stop="shapeMoveStart($event,1,1)"></div>
<div class="h-p-shape shpae-lb" @mousedown.stop="shapeMoveStart($event,0,1)"></div>
<div class="h-p-shape shpae-rt" @mousedown.stop="shapeMoveStart($event,1,0)"></div>
<div class="h-p-shape shpae-rb" @mousedown.stop="shapeMoveStart($event,0,0)"></div>
</div>
</div>
<div class="h-photo-left-msg">
<p class="l-m-f" v-if="!img">请添加图片</p>
<label class="l-m-s" v-if="img">
<input type="file" v-show="false" accept="image/jpg,image/png,image/jepg" @change="getFile($event)">
<i class="fa fa-redo"></i>
重新选择
</label>
</div>
</div>
<div class="h-photo-right">
<div class="h-photo-preview" v-show="!img">
<img class="h-photo-reget-crip" width="100px" height="100px" src="https://upload-bbs.mihoyo.com/upload/2021/06/15/74281986/d9b927bb8741dc377225c07590e0b9be_2649557922929484982.png" alt="">
</div>
<div class="h-photo-preview" v-show="img">
<!-- 放大缩小功能 -->
<img class="h-photo-reget-crip" v-if="1" :src="img" alt="" :style="{'width':width*(100/rwidth) + 'px' ,'height':height*(100/rwidth) + 'px','top':rcripTop*(100/rwidth) + 'px','left':rcripLeft*(100/rwidth) + 'px'}">
<!-- <img class="h-photo-reget-crip" v-if="height>=width" :src="img" alt="" :style="{'width':width*(100/rwidth) + 'px' ,'height':height*(100/rwidth) + 'px','top':rcripTop*(100/rwidth) + 'px','left':rcripLeft*(100/rwidth) + 'px'}"> -->
</div>
<div v-if="img" class="h-photo-right-msg">
<p>图片预览</p>
</div>
<div v-if="!img" class="h-photo-right-msg">
<p>当前头像</p>
</div>
</div>
</div>
<div class="h-photo-msg">
<p>请选择图片上传:大小200 * 200像素支持JPG、PNG等格式,图片需小于2M</p>
</div>
<canvas class="canvas" v-show="false" id="canvas" ref="canvas" width="100" height="100"></canvas>
<div class="h-photo-update">
<div class="h-photo-update" @click="cripPhoto(rcripLeft/width,rcripTop/height,rwidth/height)">更新头像</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
formData:new FormData(),
picFlag:true,
moveFlag:false,
shapeMoveFlag:false,
img:null,
imgUpdate:new Image(),
disX:0,
disY:0,
statusX:2,
statusY:2,
// save用于存储 例子:cripTopSave 用于存储cripTop的数据
top:0,
left:0,
cripTop:0,
cripLeft:0,
cripTopSave:0,
cripLeftSave:0,
rwidthSave:0,
rcripTop:0,
rcripLeft:0,
rwidth:0,
// preview top left
width:0,
height:0,
}
},
methods: {
getFile:function (e) {
var that = this;
this.file = e.target.files[0];
if (this.file.size > 2*1024*1024) {
console.log("图片不能大于2M");
return false
}
if (this.file) {
let file = this.file
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
// base64
var imgInfo = new Image()
imgInfo.src = this.result
that.img = this.result;
// 图片加载对图片进行进行填充修改
imgInfo.onload = function () {
if (imgInfo.width >= imgInfo.height) {
that.height = (imgInfo.height/imgInfo.width)*200
that.rwidth = (imgInfo.height/imgInfo.width)*200
that.width = 200
that.top = (200-(imgInfo.height/imgInfo.width)*200)/2
that.left = 0
that.cripTop = (200-(imgInfo.height/imgInfo.width)*200)/2
that.cripLeft = 0
// 存储各个属性初始值
that.cripTopSave = that.cripTop//save
that.rwidthSave = that.rwidth//save
that.cripLeftSave = that.cripLeft//save
// 初始化rcripLeft
that.rcripLeft = 0
that.rcripTop = 0
}else{
that.width = 200*(imgInfo.width/imgInfo.height)
that.rwidth = 200*(imgInfo.width/imgInfo.height)
that.height = 200
that.left = (200-200*(imgInfo.width/imgInfo.height))/2
that.top = 0
that.cripLeft = (200-200*(imgInfo.width/imgInfo.height))/2
that.cripTop = 0
// 存储各个属性
that.rwidthSave = that.rwidth//save
that.cripLeftSave = that.cripLeft//save
that.cripTopSave = that.cripTop//save
// 初始化rcriptLeft
that.rcripLeft = 0
that.rcripTop = 0
}
}
// 注意:这里的this.result中,这个this不是vue实例,而是reader对象,所以之前用that接收vue示例 that.imgSrc
};
}else{
return false
}
},
update:function(){
var baseUrl = 'https://upload-bbs.mihoyo.com/upload/2021/05/22/74281986'
this.formData.append('image', this.imgUpdate);
console.log(this.formData.get('image'));
// axios.put(url, formdata).then(function(response){
// location.href = vue.listURL;
// });
},
cripPhoto:function (x,y,z) {
var canvas = this.$refs.canvas
var ctx = canvas.getContext('2d')
var image = new Image()
image.src = this.img
let that = this
image.onload = function () {
// x,y 即定位
// ctx.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
ctx.clearRect(0,0,100,100);
ctx.drawImage(image,image.width*(-x),image.height*(-y),image.height*z,image.height*z,0,0,100,100)
let imageUrl = canvas.toDataURL("image/png");
that.imgUpdate.src = imageUrl
console.log(that.imgUpdate);
that.update()
}
},
moveStart:function (e) {
this.moveFlag = true
// 用client而不用offset的原因 优化用户体验
this.disX = e.clientX;
this.disY = e.clientY;
},
moveActive:function (e) {
if (this.moveFlag) {
// 获取鼠标移动的相对变量l t
var l = e.clientX - this.disX;
var t = e.clientY - this.disY;
// 根据获取的相对变量 以及初始位置信息在move事件中 不停地改变cripTop
var left = this.cripLeftSave
var top = this.cripTopSave
this.cripLeft = left + l
this.cripTop = top + t
this.cripTopLeftChange()
this.rcripLeft = -this.cripLeft + this.left
this.rcripTop = -this.cripTop + this.top
}
},
moveEnd:function () {
// 在move结束后更新存储位置信息,并结束move执行
if (this.moveFlag === true) {
this.cripLeftSave = this.cripLeft
this.cripTopSave = this.cripTop
this.moveFlag = false
}
},
shapeMoveStart:function (e,x,y) {
this.shapeMoveFlag = true
this.disX = e.clientX
this.statusX = x
this.statusY = y
},
shapeMoveActive:function (e) {
if (this.shapeMoveFlag) {
var l = e.clientX - this.disX
if (this.statusX === 1 && this.statusY === 1) {
this.cripTop = this.cripTopSave + l
this.cripLeft = this.cripLeftSave + l
this.rwidth = this.rwidthSave - l
}
if (this.statusX === 1 && this.statusY === 0) {
this.cripTop = this.cripTopSave - l
this.rwidth = this.rwidthSave + l
}
if (this.statusX === 0 && this.statusY === 1) {
this.cripLeft = this.cripLeftSave + l
this.rwidth = this.rwidthSave - l
}
if (this.statusX === 0 && this.statusY === 0) {
this.rwidth = this.rwidthSave + l
}
if (this.rwidth > this.height && this.width > this.height) {
this.rwidth = this.height
}
if (this.rwidth > this.width && this.height > this.width) {
this.rwidth = this.width
}
if (this.rwidth < 10) {
this.rwidth = 10
}
this.cripTopLeftChange()
this.rcripLeft = -this.cripLeft + this.left
this.rcripTop = -this.cripTop + this.top
}
},
shapeMoveEnd:function () {
if (this.shapeMoveFlag === true) {
this.shapeMoveFlag = false
this.rwidthSave = this.rwidth
this.cripLeftSave = this.cripLeft
this.cripTopSave = this.cripTop
}
},
cripTopLeftChange:function () {
if (this.cripLeft < this.left) {
this.cripLeft = this.left
}
if (this.cripLeft > this.width-this.rwidth + this.left) {
this.cripLeft = this.width-this.rwidth + this.left
}
if (this.cripTop < this.top) {
this.cripTop = this.top
}
if (this.cripTop > this.height-this.rwidth + this.top) {
this.cripTop = this.height-this.rwidth + this.top
}
},
},
}
</script>
<style scoped>
.home-photo-wrap{
width: 520px;
margin: 200px auto 0;
}
.h-photo{
position: relative;
display: inline-block;
height: 200px;
width: 200px;
}
.h-photo-left{
position: absolute;
display: inline-block;
height: 200px;
width: 200px;
top: 0;
left: 0;
background-color: #f1f2f5;
cursor: pointer;
z-index: 21;
}
.h-photo-left:hover{
background-color: #e9e9e9;
}
.h-photo-left{
transition:1s;
}
.h-photo-left-linetop{
font-weight: bold;
position: absolute;
left: calc(50% - 15px);
top: 20px;
width: 30px;
height: 160px;
border-radius: 5px;
background-color: rgb(202, 202, 202);
}
.h-photo-left-linebottem{
position: absolute;
left: 20px;
top: calc(50% - 15px);
width: 160px;
height: 30px;
border-radius: 5px;
background-color: rgb(202, 202, 202);
}
.cut-box{
position: absolute;
width: 200px;
height: 200px;
overflow: hidden;
background-color: rgb(0, 0, 0);
}
.h-photo-reget-wrap{
position: absolute;
height: 200px;
width: 200px;
}
.h-photo-reget{
position: absolute;
z-index: 18;
}
.h-photo-reget-crip{
position: absolute;
z-index: 19;
overflow: hidden;
}
.h-photo-reget-bg{
position: absolute;
height: 200px;
width: 200px;
background-color: rgba(65, 65, 65, 0.61);
z-index: 19;
}
.h-photo-reget-shape{
position: absolute;
margin-left: -2px;
margin-top: -2px;
border: 2px white solid;
z-index: 20;
cursor: all-scroll;
}
.h-p-shape{
position: absolute;
width: 5px;
height: 5px;
border: 2px white solid;
}
.shpae-lt{
left: -10px;
top: -10px;
cursor: nw-resize;
}
.shpae-lb{
left: -10px;
bottom: -10px;
cursor: ne-resize;
}
.shpae-rt{
right: -10px;
top: -10px;
cursor: ne-resize;
}
.shpae-rb{
right: -10px;
bottom: -10px;
cursor: nw-resize;
}
.h-photo-left-msg{
position: absolute;
top: 220px;
display: inline-block;
height: 20px;
width: 200px;
margin: 0 auto;
text-align: center;
}
.l-m-f{
font-size: 14px;
color: gray;
}
.l-m-s{
cursor: pointer;
font-size: 14px;
color: rgb(125, 125, 125);
}
.l-m-s:hover{
color: skyblue;
}
/* right */
.h-photo-right{
display: inline-block;
position: relative;
height: 200px;
width: 200px;
border-left: 1px solid rgb(218, 218, 218);
padding-left: 40px;
margin-left: 40px;
}
.h-photo-preview{
border-radius: 50%;
position: absolute;
height: 100px;
width: 100px;
left: 100px;
top: 100px;
margin-left: -50px;
margin-top: -50px;
overflow: hidden;
}
.h-photo-right-msg{
position: absolute;
top: 170px;
left: 78px;
font-size: 12px;
color: gray;
}
/* msg */
.h-photo-msg,.h-photo-update{
width: 600px;
height: 40px;
margin: 0 auto;
font-size: 14px;
text-align: center;
}
.h-photo-msg>p{
margin-top: 80px;
line-height: 40px;
color: #99a2aa;
}
/* update */
.h-photo-update{
cursor: pointer;
margin: 0 auto;
line-height: 40px;
height: 40px;
width: 100px;
margin-top: 20px;
background-color: #00a1d6;
border-radius: 5px;
color: white;
}
</style>