canvas 写一个框文字的组件

32 阅读3分钟

组件效果

image.png

代码

这个组件,项目里是用来框住文字,擦除水印的,把框住的坐标给后端,效果如图,具体看代码吧

<template>
  <div class="erase-canvas">
    <canvas
      ref="eraseCanvas"
      @mousedown="startDrawing"
      @mousemove="moveDraw"
      @mouseup="stopDrawing"
      @mouseleave="leaveDrawing"
      class="erase-canvas"
    ></canvas>
  </div>
</template>
<script setup>
import { ref } from "vue";
const props = defineProps({
  canvasWidth: {
    type: Number,
    default: 0,
  },
  canvasHeight: {
    type: Number,
    default: 0,
  },
});
const eraseCanvas = ref(null);
const eraseCtx = ref(null);

const activeIndex = ref(0);
const eraseBoxes = ref([]);
let dx = 0;
let dy = 0;
let handleRadius = 10;
let activePointList = [];
let defaultWidth;
let defaultHeight;
// 初始化画布
const initCanvas = () => {
  const canvas = eraseCanvas.value;
  canvas.width = props.canvasWidth;
  canvas.height = props.canvasHeight;
  defaultWidth = canvas.width * 0.2;
  defaultHeight = canvas.height * 0.1;
  eraseCtx.value = canvas.getContext("2d");
  const ctx = eraseCtx.value;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // ctx.fillStyle = '#dedede';
  // ctx.fillRect(0, 0, canvas.width, canvas.height);
};
function windowToCanvas(x, y, rect) {
  // 获取Canvas的边界矩形
  const topCanvas = eraseCanvas.value;

  // 计算缩放比例
  const scaleX = topCanvas.width / rect.width;
  const scaleY = topCanvas.height / rect.height;

  // 转换为Canvas坐标
  const canvasX = (x - rect.left) * scaleX;
  const canvasY = (y - rect.top) * scaleY;

  return { x: canvasX, y: canvasY };
}
// 检查点是否在矩形内
function isPointInRect(x, y) {
  const box = eraseBoxes.value;
  let index = -1;
  for (let i = box.length - 1; i >= 0; i--) {
    const rect = box[i];
    // console.log(
    //   x >= rect.x &&
    //     x <= rect.x + rect.width &&
    //     y >= rect.y &&
    //     y <= rect.y + rect.height,
    //   x,
    //   y,
    //   box[i]
    // );
    if (
      x >= rect.x &&
      x <= rect.x + rect.width &&
      y >= rect.y &&
      y <= rect.y + rect.height
    ) {
      activeIndex.value = i;
      index = i;
      break;
    }
  }
  return index;
}
// 检测是否点击边框上的点,进行缩放
function isPointInScaleHandle(x, y) {
  const obj = {
    isIn: false,
    pointIndex: -1,
  };
  const point = eraseBoxes.value[activeIndex.value];
  activePointList = [
    { x: point.x, y: point.y },
    { x: point.x + point.width, y: point.y },
    { x: point.x + point.width, y: point.y + point.height },
    { x: point.x, y: point.y + point.height },
  ];
  for (let i = 0; i < activePointList.length; i++) {
    const dx = activePointList[i].x - x;
    const dy = activePointList[i].y - y;
    // console.log(point.x, x, point.y, y, "点");
    if (dx * dx + dy * dy <= handleRadius * handleRadius) {
      obj.isIn = true;
      obj.pointIndex = i;
      break;
    }
  }
  return obj;
}
const draw = () => {
  const canvas = eraseCanvas.value;
  const ctx = eraseCtx.value;

  // 清除Canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  const erase = eraseBoxes.value;
  for (let i = 0; i < erase.length; i++) {
    if (activeIndex.value === i) {
      createRect(erase[i]);
    } else {
      createDashRect(erase[i]);
    }
  }
};
let pointIndexs = -1;
// 是否开始移到
let isDragging = false;
let isScale = false;
let lastX = 0;
let lastY = 0;
let oppositeCorner = null;
const startDrawing = (e) => {
  const canvas = eraseCanvas.value;
  const rect = canvas.getBoundingClientRect();
  const { x, y } = windowToCanvas(e.clientX, e.clientY, rect);
  const { isIn, pointIndex } = isPointInScaleHandle(x, y);
  console.log(isIn, pointIndex);
  if (isIn) {
    // canvas.style.cursor = 'crosshair';
    pointIndexs = pointIndex;
    oppositeCorner = activePointList[(pointIndex + 2) % 4];
    lastX = x;
    lastY = y;
    console.log("开始缩放");
    isScale = true;
    return;
  }
  const index = isPointInRect(x, y);
  if (index > -1) {
    // canvas.style.cursor = 'all-scroll';
    isDragging = true;
    console.log("开始拖动");
    dx = x - eraseBoxes.value[index].x;
    dy = y - eraseBoxes.value[index].y;
    draw();
    return;
  }
};

const moveDraw = (e) => {
  const canvas = eraseCanvas.value;
  const rect = canvas.getBoundingClientRect();
  const { x, y } = windowToCanvas(e.clientX, e.clientY, rect);
  if (eraseBoxes.value.length) {
    const { isIn } = isPointInScaleHandle(x, y);
    if (isPointInRect(x, y) > -1) {
      canvas.style.cursor = 'all-scroll';
    } else if (isIn) {
      canvas.style.cursor = 'crosshair';
    } else {
      canvas.style.cursor = 'default';
    }
  }
  
  if (isDragging) {
    eraseBoxes.value[activeIndex.value].x = x - dx;
    eraseBoxes.value[activeIndex.value].y = y - dy;
    // console.log("正在拖动");
    draw();
  }
  if (isScale) {
    let boxX = x - lastX;
    let boxY = y - lastY;
    // console.log("正在缩放", boxX);
    const activeBox = eraseBoxes.value[activeIndex.value];
    console.log("缩放中", pointIndexs);
    // 计算对应点的旋转角度
    switch (pointIndexs) {
      case 0:
        activeBox.width = Math.max(50, activeBox.width + boxX * -1);
        activeBox.height = Math.max(50, activeBox.height + boxY * -1);
        activeBox.x = oppositeCorner.x - activeBox.width
        activeBox.y = oppositeCorner.y - activeBox.height
        break;
      case 1:
        activeBox.width = Math.max(50, activeBox.width + boxX * 1);
        activeBox.height = Math.max(50, activeBox.height + boxY * -1);
        activeBox.y = oppositeCorner.y - activeBox.height
        break;
      case 2:
        activeBox.width = Math.max(50, activeBox.width + boxX * 1);
        activeBox.height = Math.max(50, activeBox.height + boxY * 1);
        break;
      case 3:
        activeBox.x = oppositeCorner.x - activeBox.width
        activeBox.width = Math.max(50, activeBox.width + boxX * -1);
        activeBox.height = Math.max(50, activeBox.height + boxY * 1);
        break;
    }

    lastX = x;
    lastY = y;
    draw();
  }
};
const stopDrawing = (e) => {
  isDragging = false;
  isScale = false;
};
const leaveDrawing = (e) => {
  isDragging = false;
  isScale = false;
};

const addRect = (type) => {
  // const rect = eraseCanvas.value.getBoundingClientRect();
  const canvas = eraseCanvas.value;
  // let len = eraseBoxes.value.length;
  // 设置默认擦除框大小和位置

  const newBox = {
    type,
    x: (canvas.width - defaultWidth) / 2,
    y: (canvas.height - defaultHeight) / 2,
    width: defaultWidth,
    height: defaultHeight,
  };
  eraseBoxes.value.push(newBox);
  // if (eraseBoxes.value.length === 0) {
  //   activeRect.value = { ...newBox }
  // }
  // createRect(newBox);
  activeIndex.value = eraseBoxes.value.length - 1;
  draw();
};
// 初始化变量
let dashLength = 15;
let gapLength = 10;
let lineWidth = 3;
// 画虚线矩形
const createDashRect = (box) => {
  const ctx = eraseCtx.value;
  // 绘制矩形
  ctx.beginPath();
  // 设置虚线样式
  ctx.setLineDash([dashLength, gapLength]);
  ctx.fillStyle = 'rgba(52, 152, 219, 0.2)';
  ctx.fillRect(box.x, box.y, box.width, box.height);
  ctx.lineWidth = lineWidth;
  ctx.strokeStyle = "#3498db";
  ctx.lineCap = "round";
  ctx.rect(box.x, box.y, box.width, box.height);
  ctx.closePath();
  ctx.stroke();
  // 重置虚线设置
  ctx.setLineDash([]);
  createText('文字擦除', box.x, box.y - 20)
};

// 画实线线矩形
const createRect = (box) => {
  // console.log("createRect", box);
  const ctx = eraseCtx.value;

  // 绘制矩形
  ctx.beginPath();
  // ctx.moveTo(box.x, box.y);
  // ctx.lineTo(box.x + box.width, box.y);
  // ctx.lineTo(box.x + box.width, box.y + box.height);
  // ctx.lineTo(box.x, box.y + box.height);
  // 设置样式
  ctx.lineWidth = 3;
  ctx.fillStyle = 'rgba(52, 152, 219, 0.2)';
  ctx.fillRect(box.x, box.y, box.width, box.height);
  
  ctx.strokeStyle = "rgba(52, 152, 219, 1)";
  ctx.rect(box.x, box.y, box.width, box.height);
  ctx.stroke();
  createPoint(box);
  createText('文字擦除', box.x, box.y - 20)
};
const createPoint = (box) => {
  const ctx = eraseCtx.value;
  const handleRadius = 10; // 边框上的点半径

  ctx.beginPath();
  ctx.arc(box.x, box.y, handleRadius, 0, Math.PI * 2);
  ctx.fillStyle = "white";
  ctx.fill();

  ctx.beginPath();
  ctx.arc(box.x + box.width, box.y, handleRadius, 0, Math.PI * 2);
  ctx.fillStyle = "white";
  ctx.fill();

  ctx.beginPath();
  ctx.arc(box.x + box.width, box.y + box.height, handleRadius, 0, Math.PI * 2);
  ctx.fillStyle = "white";
  ctx.fill();

  ctx.beginPath();
  ctx.arc(box.x, box.y + box.height, handleRadius, 0, Math.PI * 2);
  ctx.fillStyle = "white";
  ctx.fill();
};
// 绘制文本
const createText = (text, x, y) => {
  drawRoundedRect(x, y - 20, 80, 30, 4)
  // ctx.save()
  
  const ctx = eraseCtx.value;
  ctx.fillStyle = "white";
  ctx.font = "15px Arial";
  ctx.fillText(text, x + 10, y);

  // ctx.fill();
  
};
// 绘制圆角矩形
function drawRoundedRect(x, y, width, height, radius) {
  const ctx = eraseCtx.value;
  ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'
  ctx.beginPath();
  ctx.moveTo(x + radius, y);
  ctx.lineTo(x + width - radius, y);
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  ctx.lineTo(x + width, y + height - radius);
  ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  ctx.lineTo(x + radius, y + height);
  ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  ctx.lineTo(x, y + radius);
  ctx.quadraticCurveTo(x, y, x + radius, y);
  ctx.closePath();
  ctx.fill();
}
const removeRect = (index) => {
  eraseBoxes.value.splice(index, 1);
  if (activeIndex.value === index) {
    activeIndex.value = activeIndex.value.length - 1;
  }
  draw();
};
const editEraseBox = (index) => {
  activeIndex.value = index
  draw();
}
const resetCanvas = () => {
  const ctx = eraseCtx.value;
  if (!ctx) return
  const canvas = eraseCanvas.value
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  eraseBoxes.value = []
  activeIndex.value = 0
}
defineExpose({
  initCanvas,
  eraseBoxes,
  addRect,
  removeRect,
  editEraseBox,
  resetCanvas
});
</script>

<style scoped lang="scss">
.erase-canvas {
  position: absolute;
  height: 100%;
  width: 100%;
  top: 0;
  left: 0;
  z-index: 99;
  .erase-canvas {
    height: 100%;
    width: 100%;
  }
}
</style>