在线绘图小工具

文章说明

本文主要是在看了袁老师的canvas绘图小视频后所写,记录一个简单的canvas绘图功能,并学习一下较为传统的JavaScript事件写法,同时了解一下拖拽事件的更合理写法,等待后续将头像上传功能进行优化。

参考教程

程序源码

主要为核心的绘图类Shape.js,以及页面和事件,在App.vue里面

Shape.js

export class Shape {
    constructor(color, startX, startY, dpr) {
        this.endX = this.startX;
        this.endY = this.startY;
        this.color = color;
        this.startX = startX;
        this.startY = startY;
        this.dpr = dpr;
    }

    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);
    }

    draw(ctx) {
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.strokeStyle = "#fff";
        ctx.lineWidth = 3 * this.dpr;
        ctx.lineCap = "square";
        ctx.stroke();
    }

    drawDashed(ctx) {
        ctx.strokeStyle = "#fff";
        ctx.lineWidth = 1 * this.dpr;
        ctx.lineCap = "square";
        ctx.stroke();
    }

    inSide(x, y) {
        return (this.minX <= x && this.maxX >= x) && (this.minY <= y && this.maxY >= y);
    }

    // 上1、下2、左4、右8
    // 得到上1、下2、左4、左上5、左下6、右8、右上9、右下10
    inGap(gap, 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;
    }

    mouseMoveCreate(e, rect) {
        const clickX = e.clientX - rect.left;
        const clickY = e.clientY - rect.top;
        this.endX = clickX;
        this.endY = clickY;
        this.startX = this.minX;
        this.endX = this.maxX;
        this.startY = this.minY;
        this.endY = this.maxY;
    }

    mouseMoveChangePos(e, rect, startX, startY, endX, endY, clickX, clickY, canvas) {
        const disX = e.clientX - rect.left - clickX;
        const disY = e.clientY - rect.top - clickY;
        this.startX = startX + disX;
        this.startY = startY + disY;
        this.endX = endX + disX;
        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;
        }
    }
}

简单实现了矩形和椭圆形的绘制相关功能

import {Shape} from "@/Shape";

export class Rectangle extends Shape {
    constructor(color, startX, startY, dpr) {
        super(color, startX, startY, dpr);
    }

    draw(ctx) {
        ctx.beginPath();
        ctx.setLineDash([]);
        ctx.moveTo(this.minX, this.minY);
        ctx.lineTo(this.maxX, this.minY);
        ctx.lineTo(this.maxX, this.maxY);
        ctx.lineTo(this.minX, this.maxY);
        ctx.lineTo(this.minX, this.minY);
        super.draw(ctx);
    }
}
import {Shape} from "@/Shape";

export class Circle extends Shape {
    constructor(color, startX, startY, dpr) {
        super(color, startX, startY, dpr);
    }

    draw(ctx) {
        ctx.beginPath();
        ctx.setLineDash([]);
        ctx.ellipse(
            (this.maxX + this.minX) / 2,
            (this.maxY + this.minY) / 2,
            (this.maxX - this.minX) / 2,
            (this.maxY - this.minY) / 2,
            0, 0, Math.PI * 2
        );
        super.draw(ctx);

        ctx.beginPath();
        ctx.moveTo(this.minX, this.minY);
        ctx.setLineDash([1, 5]);
        ctx.lineTo(this.maxX, this.minY);
        ctx.lineTo(this.maxX, this.maxY);
        ctx.lineTo(this.minX, this.maxY);
        ctx.lineTo(this.minX, this.minY);
        super.drawDashed(ctx);
    }
}

App.vue

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

let canvas;
let rect;
let ctx;
let color;
let dpr;
let shapes = [];
let change = true;
let gap;

function setCanvas() {
  canvas.width = 1000 * dpr;
  canvas.height = 500 * dpr;
  rect = canvas.getBoundingClientRect();
}

function resizeShape(ratio) {
  for (let i = 0; i < shapes.length; i++) {
    shapes[i].startX *= ratio;
    shapes[i].startY *= ratio;
    shapes[i].endX *= ratio;
    shapes[i].endY *= ratio;
  }
}

function draw() {
  if (change) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (let i = 0; i < shapes.length; i++) {
      shapes[i].draw(ctx);
    }
    change = false;
  }
  requestAnimationFrame(draw);
}

onMounted(() => {
  canvas = document.getElementById("canvas");
  ctx = canvas.getContext("2d");
  color = document.getElementById("color");
  dpr = window.devicePixelRatio;
  gap = 10 * dpr;
  setCanvas(canvas);
  draw();

  window.onresize = () => {
    const oldRatio = dpr;
    dpr = window.devicePixelRatio;
    gap = 10 * dpr;
    setCanvas();
    resizeShape(dpr / oldRatio);
    change = true;
    draw();
  };

  window.onkeydown = (e) => {
    if (e.ctrlKey && e.code === "KeyZ") {
      shapes.pop();
      change = true;
    }
  }

  canvas.onmousedown = mouseDown;

  canvas.ondblclick = (e) => {
    const clickX = e.clientX - rect.left;
    const clickY = e.clientY - rect.top;
    let shape;
    for (let i = shapes.length - 1; i >= 0; i--) {
      if (shapes[i].inSide(clickX, clickY)) {
        shapes[i].color = color.value;
        shape = shapes[i];
        shapes.splice(i, 1);
        break;
      }
    }
    if (shape) {
      shapes.push(shape);
    }
    change = true;
  };

  canvas.onmousemove = (e) => {
    changeSize(e);
  };
});

function changeSize(e) {
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;
  for (let i = shapes.length - 1; i >= 0; i--) {
    const inGap = shapes[i].inGap(gap, clickX, clickY);
    const {startX, startY, endX, endY} = shapes[i];
    shapes[i].mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, canvas);
    if (inGap > 0) {
      break;
    }
  }
}

function getShape(clickX, clickY) {
  for (let i = shapes.length - 1; i >= 0; i--) {
    const inGap = shapes[i].inGap(gap, clickX, clickY);
    if (inGap > 0) {
      return shapes[i];
    }
    if (shapes[i].inSide(clickX, clickY)) {
      return shapes[i];
    }
  }
  return null;
}

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

  let shape = getShape(clickX, clickY);
  if (!shape) {
    if (data.checkType === "矩形") {
      shape = new Rectangle(color.value, clickX, clickY, dpr);
    } else if (data.checkType === "椭圆形") {
      shape = new Circle(color.value, clickX, clickY, dpr);
    }
    shapes.push(shape);
    canvas.onmousemove = (e) => {
      shape.mouseMoveCreate(e, rect);
      change = true;
    };
  } else {
    const {startX, startY, endX, endY} = shape;
    const inGap = shape.inGap(gap, clickX, clickY);
    if (inGap > 0) {
      canvas.onmousemove = (e) => {
        shape.mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, canvas);
        change = true;
      };
    } else {
      canvas.onmousemove = (e) => {
        shape.mouseMoveChangePos(e, rect, startX, startY, endX, endY, clickX, clickY, canvas);
        change = true;
      };
    }
  }

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

const data = reactive({
  typeList: [
    {
      id: 1,
      name: "矩形",
    },
    {
      id: 2,
      name: "椭圆形",
    },
  ],
  checkType: "矩形",
});

function select(item) {
  data.checkType = item.name;
}
</script>

<template>
  <div style="margin: 100px auto; display: flex; align-items: center; justify-content: center; flex-direction: column">
    <ul class="type">
      <li v-for="item in data.typeList" :key="item.id" :class="data.checkType === item.name ? 'selected' : ''"
          @click="select(item)">
        {{ item.name }}
      </li>
    </ul>
    <input id="color" style="margin-bottom: 20px" type="color"/>
    <canvas id="canvas"></canvas>
  </div>
</template>

<style>
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

.type {
  list-style: none;

  li {
    padding: 20px;
    float: left;

    &:hover {
      color: #ff0000aa;
      cursor: pointer;
    }
  }

  .selected {
    color: #ff0000;
  }
}

#canvas {
  background-color: coral;
}
</style>

功能展示

绘制矩形和椭圆形
在这里插入图片描述

拖动矩形和椭圆形
在这里插入图片描述

放大缩小
在这里插入图片描述

双击切换颜色并让其在最上层
在这里插入图片描述

源码下载

在线绘图小工具

  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值