撸一个 vue 的截图组件,按比例截取

<template>
    <div class="clip-img" :style="imgStyle">
        <img :src="url" alt="" crossOrigin="anonymous" :style="imgStyle">
        <canvas ref="canvas" :style="imgStyle"  @mousedown="onmousedown" @mousemove="onmousemove"></canvas>
    </div>
</template>
<script>
export default {
    name:"clip-image",
    props:{
        max:{ // 缩放基准宽度或高度
            type:Number,
            default:400
        }
    },
    data(){
        return {
            img:null,
            url:null,
            imgInfo: null, // 压缩前的信息
            imgCInfo:null, // 压缩后的信息
            clipInfo:null, // 压缩前的信息
            clipCinfo:null, // 压缩后的信息
            ctx:null, // 画板
            pos:{x:0,y:0},
            lock: "", // 锁住一个方向
            boundary:null
        }
    },
    computed:{
        imgStyle(){
            let imgCInfo = this.imgCInfo;
            if(imgCInfo){
                return {
                     `${imgCInfo.w}px`,
                    height: `${imgCInfo.h}px`
                }
            }
            return {height:"0px",height:"0px",display:"none"};
        }
    },
    async mounted(){
        this.clear();
        this.ctx = this.$refs.canvas.getContext("2d");
        window.addEventListener("mouseup",this.onmouseup);
    },
    methods:{
        init(src,bound){
            this.setSrc(src)
            this.setClip(bound);
        },
        // 外部调用
        async setSrc(src){
            let img = await this.loadImage(src,{crossOrigin:"anonymous"});
            this.img = img;
            this.setImg(img);
        },
        clear(){
             let obj = {
                img:null,
                url:null,
                imgInfo: null, // 压缩前的信息
                imgCInfo:null, // 压缩后的信息
                clipInfo:null, // 压缩前的信息
                clipCinfo:null, // 压缩后的信息
                ctx:null, // 画板
                pos:{x:0,y:0},
                lock: "", // 锁住一个方向
                boundary:null
             }
             for(let i in obj){
                 this[i]  = obj[i];
             }
        },
        loadImage(url,attrs){
            this.url = url;
            let img = new Image();
            img.src = url;
            attrs = attrs || {};
            for(let i in attrs){
                img[i] = attrs[i];
            }
            return new Promise((resolve,reject)=>{
                img.onload = function(){
                    resolve(img);
                };
                img.onerror  = reject;
            });
        },
        setImg(img){
            this.img = img;
            this.imgInfo = {
                w:img.width,
                h:img.height
            };
            
            // 压缩图的比例
            let w,h,scale;
            if(img.width > img.height){
                w = this.max;
                scale = w/img.width;
                h = scale*img.height;
            }else{
                h = this.max;
                scale = (h/img.height);
                w = scale * img.width;
            }
            this.imgCInfo = {
                w,
                h,
                scale
            };
            let canvas =  this.$refs.canvas;
            canvas.width = w;
            canvas.height = h;
            this.setClip();
        },
        setClip(clipInfo){
            if(clipInfo){
                this.clipInfo = {
                    w:clipInfo.width,
                    h:clipInfo.height
                };
            }
            if(this.imgCInfo && this.clipInfo){
                this.compressClip(this.imgCInfo,this.clipInfo);
                this.fill();
            }
        },
        compressClip(imgCInfo,clipInfo){
            // 压缩缩放
            let w,h,scale;
            let imgR = imgCInfo.w/imgCInfo.h;
            let clipR = clipInfo.w / clipInfo.h;

            if(imgR > clipR){
                // 图片的宽度偏大
                h = imgCInfo.h;
                scale = h/clipInfo.h;
                w = scale * clipInfo.w;
                this.lock = "h";
            }else{
                // 图片的宽度偏小
                w = imgCInfo.w;
                scale = w/clipInfo.w;
                h = scale * clipInfo.h;
                this.lock = "w";
            }
            this.clipCinfo = {
                w,
                h,
                scale
            }
            this.boundary = {
                w:this.imgCInfo.w - this.clipCinfo.w,
                h:this.imgCInfo.h - this.clipCinfo.h
            };
            this.pos = {
                x:0,
                y:0
            };
        },
        onmouseup(){
            this.mouse = null;
        },
        onmousedown(e){
            this.mouse = {
                x:e.offsetX,
                y:e.offsetY
            }
        },
        onmousemove(e){
            if(this.mouse){
                let x = e.offsetX - this.mouse.x ;
                let y = e.offsetY - this.mouse.y;
                if(this.lock == "h"){
                    x = this.pos.x + x;
                    if(x < 0){
                        x = 0;
                    }else if(x > this.boundary.w){
                        x = this.boundary.w
                    }
                    this.pos.x = x;
                }else{
                    y = this.pos.y + y;
                    if(y < 0){
                        y = 0;
                    }else if(y > this.boundary.h){
                        y = this.boundary.h
                    }
                    this.pos.y = y;
                }
                this.mouse = {
                    x:e.offsetX,
                    y:e.offsetY
                }
                this.fill();
            }
        },
        fill(){
            let {w,h} =  this.clipCinfo;
            let {x,y} = this.pos;
            let clipctx = this.ctx;
            let imgCInfo = this.imgCInfo;
            clipctx.clearRect(0, 0, imgCInfo.w, imgCInfo.h);
            clipctx.beginPath();
            clipctx.fillStyle = 'rgba(0,0,0,0.6)';
            clipctx.strokeStyle = "green";
            //遮罩层
            clipctx.globalCompositeOperation = "source-over";
            clipctx.fillRect(0, 0, imgCInfo.w, imgCInfo.h);
            //画框
            clipctx.globalCompositeOperation = 'destination-out';
            clipctx.fillRect(x, y, w, h);
            //描边
            clipctx.globalCompositeOperation = "source-over";
            clipctx.moveTo(x, y);
            clipctx.lineTo(x + w, y);
            clipctx.lineTo(x + w, y + h);
            clipctx.lineTo(x, y + h);
            clipctx.lineTo(x, y);
            clipctx.stroke();
            clipctx.closePath();
        },
        exportBase(){
            // 导出图片 base64
            let pos = this.pos;
            let scale = this.imgCInfo.scale;
            let sx = pos.x / scale;
            let sy = pos.y / scale;
            let swidth = parseInt(this.clipCinfo.w / scale);
            let sheight = parseInt(this.clipCinfo.h / scale);
            let canvas = document.createElement("canvas");
            canvas.width = swidth;
            canvas.height = sheight;
            let ctx = canvas.getContext("2d");  
            ctx.drawImage(this.img,sx,sy,this.imgInfo.w,this.imgInfo.h,0,0,this.imgInfo.w,this.imgInfo.h);
            return  canvas.toDataURL("image/png");
        },
        dataURLtoFile(b64Data,filename){
            filename = filename || "test.png";
            let mime = "image/png";
            var bstr = atob(b64Data.replace(/^data:image/(png|jpeg|jpg);base64,/, ''));
            var n = bstr.length;
            var u8arr = new Uint8Array(n);
            while(n--){
                u8arr[n] = bstr.charCodeAt(n);
            }
            // 转换成file对象
            return new File([u8arr], filename, {type:mime});

            // 转换成成blob对象
            // return new Blob([u8arr],{type:mime});
            // return blob;
        }
    },
    destroyed(){
        window.removeEventListener("mouseup",this.onmouseup);
    }
}
</script>
<style lang="scss" scoped>
.clip-img{
    border: 1px solid red;
    margin:  20px auto;
    position: relative;
    height: 0;
     0;
    overflow: hidden;
    canvas{
        position: absolute;
        left: 0;
        top: 0;
        z-index: 1;
        cursor: move;
    }
    img{
        position: relative;
        z-index: 0;
    }
}
</style>

  

使用:

<template>
    <div class="clip-img">
        <clipImage ref="clipImage"></clipImage>
        <button @click="getImg">导出</button> <button @click="slide">切换</button>
        <img :src="src" v-if="src" alt="" class="result">
    </div>
</template>
<script>
import clipImage from "@/components/clip-image.vue";
export default {
    name:"clip-image",
    data(){
        return {
            src:""
        }
    },
    components:{
        clipImage
    },
    mounted(){
        // setSrc
        this.$refs.clipImage.init("xxx",{400,height:200});
    },
    methods:{
        getImg(){
            this.src = this.$refs.clipImage.exportBase();
            console.log("截图成功")
        },
        slide(){
            this.$refs.clipImage.setSrc("xxx");
        }
    }
}
</script>
<style lang="scss" scoped>
.clip-img{
    .result{
        max- 400px;
    }
}
</style>

  

原文地址:https://www.cnblogs.com/muamaker/p/12051295.html