修正版头像上传组件

发布于:2024-07-16 ⋅ 阅读:(142) ⋅ 点赞:(0)

文章说明

头像剪切上传一文中,我采用div做裁剪效果,感觉会有一些小问题,在昨天基于canvas绘制的功能中改进了一版,让代码变得更简洁,而且通用性相对高一些,源码及效果展示如下;包含拖拽和调整裁剪框的效果

核心源码展示

主要包括App.vue中的元素和事件,以及Rectangle.js内的绘图方法和鼠标移动事件

App.vue

<script setup>
import {nextTick, reactive} from "vue";
import {Rectangle} from "@/Rectangle";

const data = reactive({
  selectFile: false,
  imgWidth: 0,
  imgHeight: 0,
});

let image;
let leftCanvas;
let leftContext;
let rightCanvas;
let rightContext;
let rect;
let rectangle;

async function selectFile() {
  const pickerOpts = {
    types: [
      {
        description: "Images",
        accept: {
          "image/*": [".png", ".jpeg", ".jpg"],
        },
      },
    ],
    excludeAcceptAllOption: true,
    multiple: false,
  };
  const fileHandle = await window.showOpenFilePicker(pickerOpts);
  const file = await fileHandle[0].getFile();

  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = function (e) {
    data.src = e.target.result;
    data.selectFile = true;

    image = new Image();
    image.src = e.target.result;

    nextTick(() => {
      data.imgWidth = image.width;
      data.imgHeight = image.height;

      nextTick(() => {
        leftCanvas = document.getElementsByClassName("left-canvas")[0];
        rect = leftCanvas.getBoundingClientRect();

        rightCanvas = document.getElementsByClassName("right-canvas")[0];
        leftContext = leftCanvas.getContext("2d");
        rightContext = rightCanvas.getContext("2d");
        rectangle = new Rectangle(data.imgWidth, data.imgHeight);
        change = true;
        draw();

        leftCanvas.onmousedown = (e) => {
          omMouseDown(e);
        };
        leftCanvas.onmousemove = (e) => {
          changeSize(e);
        };
      });
    });
  };
}

let change;

function draw() {
  if (change) {
    drawLeftImage(image, rectangle);
    drawRightImage(image, rectangle);
    change = false;
  }
  requestAnimationFrame(draw);
}

function drawLeftImage(image, rectangle) {
  leftContext.drawImage(image, 0, 0, data.imgWidth, data.imgHeight, 0, 0, data.imgWidth, data.imgHeight);
  rectangle.draw(leftContext);
}

function drawRightImage(image, rectangle) {
  rightContext.drawImage(image, rectangle.startX, rectangle.startY, rectangle.width, rectangle.height, 0, 0, rightCanvas.width, rightCanvas.height);
}

function omMouseDown(e) {
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;

  const {startX, startY, endX, endY} = rectangle;
  const inGap = rectangle.inGap(clickX, clickY);
  if (inGap > 0) {
    leftCanvas.onmousemove = (e) => {
      rectangle.mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, leftCanvas);
      change = true;
    };
  } else {
    leftCanvas.onmousemove = (e) => {
      rectangle.mouseMoveChangePos(e, rect, startX, startY, endX, endY, clickX, clickY, leftCanvas);
      change = true;
    };
  }

  window.onmouseup = () => {
    leftCanvas.onmousemove = null;
    leftCanvas.onmousemove = (e) => {
      changeSize(e);
    };
  };
}

function changeSize(e) {
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;
  const inGap = rectangle.inGap(clickX, clickY);
  const {startX, startY, endX, endY} = rectangle;
  rectangle.mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, leftCanvas);
}
</script>

<template>
  <div class="avatar-container">
    <div class="img-container">
      <div v-show="!data.selectFile" class="select-file" @click="selectFile">
        <p>jpg/png file with a size less than 5MB<em>click to upload</em></p>
      </div>
      <canvas v-show="data.selectFile" :height="data.imgHeight" :width="data.imgWidth" class="left-canvas"></canvas>
    </div>

    <canvas class="right-canvas" height="100" width="100"></canvas>
  </div>
</template>

<style scoped>
.avatar-container {
  margin: 0 auto;
  width: fit-content;
  user-select: none;
  display: flex;
  justify-content: center;
  align-items: center;
  padding-top: 100px;

  .img-container {
    position: relative;
    width: fit-content;

    .select-file {
      width: 500px;
      height: 300px;
      border: 1px dashed #dcdfe6;
      border-radius: 20px;
      display: flex;
      justify-content: center;
      align-items: center;

      &:hover {
        border: 1px dashed #409eff;
        cursor: pointer;
      }

      p {
        font-size: 14px;
        color: #606266;

        em {
          color: #409eff;
          font-style: normal;
          margin-left: 5px;
        }
      }
    }
  }

  .left-canvas {
    margin-left: 30px;
    border: 1px dashed #409eff;
    float: left;
  }

  .right-canvas {
    margin-left: 30px;
    border: 1px dashed #409eff;
    float: left;
  }
}
</style>

Rectangle.js

const gap = 10;
const initValue = 100;

export class Rectangle {
    constructor(imageWidth, imageHeight) {
        const startX = (imageWidth - initValue) / 2;
        const startY = (imageHeight - initValue) / 2;
        this.startX = startX;
        this.startY = startY;
        this.endX = this.startX + initValue;
        this.endY = this.startY + initValue;
        this.imageWidth = imageWidth;
        this.imageHeight = imageHeight;
    }

    get minX() {
        return Math.min(this.startX, this.endX);
    }

    get maxX() {
        return Math.max(this.startX, this.endX);
    }

    get minY() {
        return Math.min(this.startY, this.endY);
    }

    get maxY() {
        return Math.max(this.startY, this.endY);
    }

    get width() {
        return this.maxX - this.minX;
    }

    get height() {
        return this.maxY - this.minY;
    }

    draw(ctx) {
        ctx.beginPath();
        ctx.moveTo(this.minX, this.minY);
        ctx.setLineDash([3, 2]);
        ctx.lineTo(this.maxX, this.minY);
        ctx.lineTo(this.maxX, this.maxY);
        ctx.lineTo(this.minX, this.maxY);
        ctx.lineTo(this.minX, this.minY);
        ctx.strokeStyle = "#409eff";
        ctx.lineWidth = 1;
        ctx.lineCap = "square";
        ctx.stroke();
    }

    // 上1、下2、左4、右8
    // 得到上1、下2、左4、左上5、左下6、右8、右上9、右下10
    inGap(x, y) {
        let result = 0;
        if (Math.abs(this.minY - y) <= gap && (this.minX - gap <= x && this.maxX + gap >= x)) {
            result += 1;
        }
        if (Math.abs(this.maxY - y) <= gap && (this.minX - gap <= x && this.maxX + gap >= x)) {
            result += 2;
        }
        if (Math.abs(this.minX - x) <= gap && (this.minY - gap <= y && this.maxY + gap >= y)) {
            result += 4;
        }
        if (Math.abs(this.maxX - x) <= gap && (this.minY - gap <= y && this.maxY + gap >= y)) {
            result += 8;
        }
        return result;
    }

    mouseMoveChangePos(e, rect, startX, startY, endX, endY, clickX, clickY, canvas) {
        const disX = e.clientX - rect.left - clickX;
        const disY = e.clientY - rect.top - clickY;
        if (startX + disX >= 0) {
            this.startX = startX + disX;
        }
        if (endX + disX <= this.imageWidth) {
            this.endX = endX + disX;
        }
        if (startY + disY >= 0) {
            this.startY = startY + disY;
        }
        if (endY + disY <= this.imageHeight) {
            this.endY = endY + disY;
        }
        canvas.style.cursor = "move";
    }

    mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, canvas) {
        const disX = e.clientX - rect.left - clickX;
        const disY = e.clientY - rect.top - clickY;
        if (endX + disX < startX || endY + disY < startY || startX + disX > endX || startY + disY > endY) {
            return;
        }

        switch (inGap) {
            case 1:
                canvas.style.cursor = "n-resize";
                this.startY = startY + disY;
                break;
            case 2:
                canvas.style.cursor = "s-resize";
                this.endY = endY + disY;
                break;
            case 4:
                canvas.style.cursor = "w-resize";
                this.startX = startX + disX;
                break;
            case 5:
                canvas.style.cursor = "nw-resize";
                this.startX = startX + disX;
                this.startY = startY + disY;
                break;
            case 6:
                canvas.style.cursor = "sw-resize";
                this.startX = startX + disX;
                this.endY = endY + disY;
                break;
            case 8:
                canvas.style.cursor = "e-resize";
                this.endX = endX + disX;
                break;
            case 9:
                canvas.style.cursor = "ne-resize";
                this.endX = endX + disX;
                this.startY = startY + disY;
                break;
            case 10:
                canvas.style.cursor = "se-resize";
                this.endX = endX + disX;
                this.endY = endY + disY;
                break;
            default:
                canvas.style.cursor = "default";
                break;
        }
    }
}

运行效果展示

点击选择图片
在这里插入图片描述

可以拖动裁剪框
在这里插入图片描述

可以调整裁剪框大小
在这里插入图片描述

源码下载

头像上传组件


网站公告

今日签到

点亮在社区的每一天
去签到