vue实现一个仿QQ头像裁剪

91 阅读3分钟

公司有需求要做一个类似于QQ的头像裁剪。经过网上搜索发现没有资源,只好自己手动实现了一个。直接上代码

<template>
    <div>
        <div class="clip-page">
            <div class="pic_container" style="width: 160px; height: 160px; position: relative; overflow: hidden">
                <div style="position: absolute; top: 0; left: 0; width: 160px; height: 160px">
                    <div class="dragBox" style="position: absolute; top: 0; left: 0; z-index: 999; cursor: pointer">
                        <!-- 底图 -->
                        <img class="pic-warp" :src="img" alt="" :style="{ 'transform':`scale(${scale})`}" draggable="false" />
                       
                    </div>
                </div>

                <!-- 遮罩 -->
                <div class="mask">
                    <!-- 圆形镂空 -->
                    <div class="circle_div">
                        
                        </div> -->
                        <div class="lightBox" style="width: 160px; height: 160px; position: absolute; left: 0; top: -35px">
                            <img class="pic" :src="img" :style="{ 'transform':`scale(${scale})`}" draggable="false"
                                style="position: absolute; left: 0; top: 0; bottom: 0; margin: auto" />
                        </div>
                    </div>
                   
                </div>
            </div>

            <!-- 用来裁剪的图 -->
            <img class="clipImg" :src="img" alt="" :style="{ 'transform':`scale(${scale})`}" style="display: none" />

            <a-space style="margin-top: 5px">
                <icon-minus @click="minus" />
                <a-slider v-model="slider" @change="fixSize" :style="{ width: '120px' }" :disabled="noScale" />
                <icon-plus @click="plus" />
            </a-space>
            <canvas id="canvas" style="display: none"></canvas>
        </div>
        <!-- 用来获取宽高的图 -->
        <img :src="img" alt="" class="imgDom" style="display: none" />
    </div>
</template>
<script lang="ts">
import { clearImageEdgeBlank } from '@/utils/clearImgBorder'
export default {
    props: ['img'],
    data() {
        return {
            noScale: false, //不能放大
            slider: 0,
            scale: 1,
            dragX: 0,
            dragY: 0,
            minScale: 1,
            WTH: 1,
            step: 0.05,
            W:0,
            H:0
        }
    },
    methods: {
        fixSize(value: any) {
            if(value === 100){
                this.scale = 2
                return
            }
            let ratio = (value / 100)
            let diff = 2 - this.minScale
            let temp = (ratio*diff)+this.minScale
            if(temp<this.scale){
                this.imgOnCenter()
            }
            this.scale = temp
            // console.log(this.scale)
        },
        //缩小
        minus() {
            if (this.noScale) return
            this.imgOnCenter()
            if (this.slider > 5) {
                this.slider -= 5
                // let oper = this.slider / 100 + 1
                // if (oper < this.minScale || oper == this.minScale) {
                //     return
                // }
                // this.scale = oper
                let slide = this.scale
                slide -= this.step
                if (slide < 1) {
                    return
                }
                this.scale -= this.step
            } else {
                this.slider = 0
                this.scale = this.minScale
            }
        },
        //放大
        plus() {
            if (this.noScale) return
            if (this.slider < 95) {
                this.slider += 5
               
                // this.scale = oper
                let slide = this.scale
                slide += this.step
                if (slide > 2) {
                    return
                }
                this.scale += this.step
            } else {
                this.slider = 100
                this.scale = this.minScale > 2 ? this.minScale : 2
            }
        },
        async clip() {
            const canvas: any = document.querySelector('#canvas')
            const ctx = canvas.getContext('2d')
            const img = document.querySelector('.clipImg')

            if(this.WTH>=1){
                canvas.width = 160 * this.WTH * (this.scale / this.minScale) * 10
                canvas.height = 160 * (this.scale / this.minScale) * 10
                ctx.rect(((this.scale / this.minScale) * 160 * this.WTH / 2 - 80 - this.dragX) * 10, ((this.scale / this.minScale)*160/2 -45  - this.dragY) * 10 , 1600, 900);
                ctx.closePath()
                ctx.clip();
                ctx.drawImage(img, 0, 0, 160 * this.WTH * (this.scale / this.minScale) * 10, 160 * (this.scale / this.minScale) * 10);

            }else if(this.WTH<1){
                canvas.width = 160 * (this.scale / this.minScale) * 10
                canvas.height = 160 / this.WTH * (this.scale / this.minScale) * 10
                ctx.rect(((this.scale / this.minScale)*160/2 - 80 - this.dragX)*10, (((this.scale / this.minScale)*(160 /this.WTH )/2) -45  - this.dragY)*10 , 1600, 900);
                ctx.closePath()
                ctx.clip();
                ctx.drawImage(img, 0, 0, 160 * (this.scale / this.minScale) * 10, 160 / this.WTH * (this.scale / this.minScale) * 10);
            }

           
            const base64 = await clearImageEdgeBlank(canvas.toDataURL('image/png'))
            console.log(base64)
           
            return base64
        },
        onDrag(e: any) {
            let box: any = document.querySelector('.dragBox')
            let lightBox: any = document.querySelector('.lightBox')

            let startX = e.clientX
            let startY = e.clientY
            let innerX = parseInt(box.style.left)
            let innerY = parseInt(box.style.top)
            console.log(this.WTH)

            document.onmousemove = (event) => {
                let x = innerX + event.clientX - startX
                let y = innerY + event.clientY - startY
                box.style.left = x + 'px'
                box.style.top = y + 'px'
                lightBox.style.left = x + 'px'
                lightBox.style.top = y - 35 + 'px'
                this.dragX = x
                this.dragY = y
                //lightBox.style.backgroundPosition = x + 'px ' + y + 'px'
                // lightBox.style.backgroundPosition.y = y + 'px'
                let leftBorder = (160 - this.scale * 160) / 2
                let rightBorder = (this.scale * 160 - 160) / 2
                let topBorder = (160 - this.scale * 160) / 2
                let bottomBorder = (this.scale * 160 - 160) / 2
                if (this.WTH > 1) {
                    topBorder = 0
                    bottomBorder = 0
                    if (this.scale > this.minScale) {
                        let plus = this.scale / this.minScale
                        topBorder = (160 - plus * 160) / 2
                        bottomBorder = (plus * 160 - 160) / 2
                    }
                }
                if (this.WTH < 1) {
                    leftBorder = 0
                    rightBorder = 0
                    if (this.scale > this.minScale) {
                        let plus = this.scale / this.minScale
                        leftBorder = (160 - plus * 160) / 2
                        rightBorder = (plus * 160 - 160) / 2
                    }
                    
                    // box.style.top = 160/this.WTH - 160 + 35 + 'px'
                    
                    // lightBox.style.top = 160/this.WTH - 160  + 'px'
                    // this.dragY = topBorder - 35
                    // return
                }

                if (x < leftBorder) {
                    box.style.left = leftBorder + 'px'
                    
                    lightBox.style.left = leftBorder + 'px'
                    this.dragX = leftBorder
                }
                if (x > rightBorder) {
                    box.style.left = rightBorder + 'px'
                   
                    lightBox.style.left = rightBorder + 'px'
                    this.dragX = rightBorder
                }
               
                
                if (this.scale >= this.minScale) {
                    if (y < topBorder - 35) {
                        box.style.top = topBorder - 35 + 'px'
                        //   lightBox.style.backgroundPosition = x + 'px ' + topBorder + 'px'
                        lightBox.style.top = topBorder - 35 - 35 + 'px'
                        this.dragY = topBorder - 35
                    }
                    if (y > bottomBorder + 35) {
                        box.style.top = bottomBorder + 35 + 'px'
                        //lightBox.style.backgroundPosition = x + 'px ' + bottomBorder + 'px'
                        lightBox.style.top = bottomBorder + 35 - 35 + 'px'
                        this.dragY = bottomBorder + 35
                    }
                } else {
                    if (y < -((160/this.WTH)/2 - 45)) {
                        // box.style.top = -35 + 'px'
                        // lightBox.style.top = -35 - 35 + 'px'
                        // this.dragY = -35
                        box.style.top = -((160/this.WTH)/2 - 45) + 'px'
                        lightBox.style.top = -((160/this.WTH)/2 - 45) -35 + 'px'
                        this.dragY = -((160/this.WTH)/2 - 45)
                        console.log(y)
                    }
                    if (y > ((160/this.WTH)/2 - 45)) {
                        box.style.top = ((160/this.WTH)/2 - 45)+ 'px'
                        //lightBox.style.backgroundPosition = x + 'px ' + bottomBorder + 'px'
                        lightBox.style.top = ((160/this.WTH)/2 - 45) - 35 + 'px'
                        this.dragY = ((160/this.WTH)/2 - 45)
                    }
                }
            }
            document.onmouseup = (e) => {
                // console.log(e.offsetX)
                document.onmousemove = null
                document.onmouseup = null
            }
        },
        imgOnCenter() {
            let box: any = document.querySelector('.dragBox')
            let lightBox: any = document.querySelector('.lightBox')
            box.style.left = 0
            box.style.top = 0
            lightBox.style.left = 0
            lightBox.style.top = -35 + 'px'
        },
    },
    mounted() {
        let container: any = document.querySelector('.pic_container')
        container.addEventListener('wheel', (e: any) => {
            //console.log(e.deltaY)
            if (this.noScale) return
            if (e.deltaY > 0) {
                if (this.slider < 95) {
                    this.slider += 5
                    
                    let slide = this.scale
                    slide += this.step
                    if (slide > 2) {
                        return
                    }
                    this.scale += this.step
                } else {
                    this.slider = 100
                    this.scale = this.minScale > 2 ? this.minScale : 2
                }
            } else {
                this.imgOnCenter()
                if (this.slider > 5) {
                    this.slider -= 5
                    
                    let slide = this.scale
                    slide -= this.step
                    if (slide < 1) {
                        return
                    }
                    this.scale -= this.step
                } else {
                    this.slider = 0
                    this.scale = this.minScale
                }
            }
        })

        //拖拽截图框
        let dragBox = document.querySelector('.circle_div')
        dragBox?.addEventListener('mousedown', this.onDrag, false)

        const imgDom: any = document.querySelector('.imgDom')
        this.$nextTick(() => { })

        setTimeout(() => {
           
            const W = this.W = imgDom.width
            const H = this.H  = imgDom.height
            this.WTH = (W / H).toFixed(2) as any
            console.log(imgDom.width,'___W')
            console.log(imgDom.height,'___h')
           
            if(this.WTH>1){
                if(W<160){
                    let s = (160/H).toFixed(2) as any
                    this.scale = s
                    this.minScale = s
                }else{
                    this.scale = W/H
                    this.minScale = W/H
                }
            }else if(this.WTH<1){
                if(H<160){
                    let s = (160/W).toFixed(2) as any
                    this.scale = s
                    this.minScale = s
                }else{
                    this.scale = H/W
                    this.minScale = H/W
                }
            }
            this.step = (2 - this.minScale) / 20
            // console.log(this.minScale)
            if (this.minScale > 2||this.minScale == 2) {
                this.noScale = true
            }
        }, 100)

       
    },
    beforeDestroy() {
        let container: any = document.querySelector('.pic_container')
        container.removeEventListener('wheel', false)
    },
}
</script>
<style scoped>
body {
    -webkit-tap-highlight-color: transparent;
    -moz-user-select: none;
    /*火狐*/
    -webkit-user-select: none;
    /*webkit浏览器*/
    -ms-user-select: none;
    /*IE10*/
    -khtml-user-select: none;
    /*早期浏览器*/
    user-select: none;
}

.clip-page {
    margin: 0 auto;
    width: 160px;
}

.mask {
    width: 160px;
    height: 160px;

    z-index: 999;
    background-color: #333;
    opacity: 0.5;
    position: absolute;
    top: 0;
    left: 0;
}

.dragBox {
    width: 160px;
    height: 160px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.pic {
    position: absolute;
    /* width: 160px;
    height: 160px; */
    max-width: 160px;
    max-height: 160px;
    /* min-height: 160px; */
    /* background-repeat: no-repeat;
    background-size: contain; */
    /* top: -35px; */
    z-index: 55;
    /* background-attachment:fixed; */
    left:0;
    top:0;
    right: 0;
    bottom: 0;
    margin: auto;
}

.pic-warp {
    position: absolute;
    /* width: 160px;
    height: 160px; */
    /* background-repeat: no-repeat;
    background-size: contain; */
    max-width: 160px;
    max-height: 160px;
    /* min-height: 160px; */
    /* width: inherit; */
}

.circle_div {
    width: 160px;
    height: 90px;
    overflow: hidden;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    z-index: 9999;
    border-top: 1px solid white;
    border-bottom: 1px solid white;
    /* background-position: 0 60px; */
    /* border: 1px solid white;
  border-radius: 50%; */
    cursor: pointer;
}
</style>


//clearImgBorder.js

//bitmap 包含关于img的各种参数信息
export function clearImageEdgeBlank(url:any, padding = 0) {
    return new Promise((resolve, reject) => {
      // create canvas
      const canvas = document.createElement("canvas");
      const ctx:any = canvas.getContext("2d");
  
      // create image
      const image = new Image();
      image.onload = draw;
      image.src = url;
      image.crossOrigin = "Anonymous";
  
      function draw() {
        canvas.width = image.width;
        canvas.height = image.height;
        
        ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const { data, width, height } = imageData;
  
        // 裁剪需要的起点和终点,初始值为画布左上和右下点互换设置成极限值。
        let startX = width,
          startY = height,
          endX = 0,
          endY = 0;
  
        /*
        col为列,row为行,两层循环构造每一个网格,
        便利所有网格的像素,如果有色彩则设置裁剪的起点和终点
        */
        for (let col = 0; col < width; col++) {
          for (let row = 0; row < height; row++) {
            // 网格索引
            const pxStartIndex = (row * width + col) * 4;
  
            // 网格的实际像素RGBA
            const pxData = {
              r: data[pxStartIndex],
              g: data[pxStartIndex + 1],
              b: data[pxStartIndex + 2],
              a: data[pxStartIndex + 3]
            };
  
            // 存在色彩:不透明
            const colorExist = pxData.a !== 0;
  
            /*
            如果当前像素点有色彩
            startX坐标取当前col和startX的最小值
            endX坐标取当前col和endX的最大值
            startY坐标取当前row和startY的最小值
            endY坐标取当前row和endY的最大值
            */
            if (colorExist) {
              startX = Math.min(col, startX);
              endX = Math.max(col, startX);
              startY = Math.min(row, startY);
              endY = Math.max(row, endY);
            }
          }
        }
  
        // 右下坐标需要扩展1px,才能完整的截取到图像
        endX += 1;
        endY += 1;
  
        // 加上padding
        startX -= padding;
        startY -= padding;
        endX += padding;
        endY += padding;
  
        // 根据计算的起点终点进行裁剪
        const cropCanvas = document.createElement("canvas");
        const cropCtx:any = cropCanvas.getContext("2d");
        cropCanvas.width = endX - startX;
        cropCanvas.height = endY - startY;
        cropCtx.drawImage(
          image,
          startX,
          startY,
          cropCanvas.width,
          cropCanvas.height,
          0,
          0,
          cropCanvas.width,
          cropCanvas.height
        );
  
        // rosolve裁剪后的图像字符串
        resolve(cropCanvas.toDataURL());
      }
    });
  }