在线绘图小工具
- 文章说明
- 程序源码
- 功能展示
- 源码下载
文章说明
本文主要是在看了袁老师的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>
功能展示
绘制矩形和椭圆形
拖动矩形和椭圆形
放大缩小
双击切换颜色并让其在最上层
源码下载
在线绘图小工具