基于vue的图片裁剪框的实现

在这里插入图片描述

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>
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值