
canvas坐标系统默认为左上角为原点,横轴x轴,向右为正,纵轴为y轴,向下为正的窗口坐标系统,可以对坐标系统坐标变换,平移、缩放、旋转、自定义变换方式等。
mousemove、mouseup和mousedown等事件获取到的是默认窗口坐标系统的事件位置。在获取绘画内容的位置时,需要转换后的坐标系统和默认窗口坐标系统转换计算。
canvas对于画笔样式使用save,restore栈的方式记录。
拓补图支持适配
通过初始化一个_w和_h的值,_w = 375, _h = 318,_unit = 1。监听页面的大小canvas的大小跟随变动,计算_unit的值。
js
let _unit = 1;
function updateCanvas() {
if (!ctx) return;
const _w = 375;
const _h = 318;
if (canvas_w / _w > canvas_h / _h) {
_unit = canvas_h / _h;
} else {
_unit = canvas_w / _w;
}
ctx.clearRect(0, 0, canvas_w, canvas_h);
ctx.scale(_unit, _unit);
ctx && draw(ctx);
}
鼠标在节点上展示手的形状
监听mousemove事件,如果鼠标在节点上显示手状。通过事件e.clientX和e.clientY的值,与节点在坐标轴上的转换值对比。
js
function bindEvent() {
canvas.value!.addEventListener('mousemove',(e: any) => {
let mouseX = e.clientX;
let mousY = e.clientY;
checkMousePos(mouseX, mousY);
})
}
function checkMousePos(mouseX: number, mouseY: number) {
for (let i = 0 ; i < imgList.length; i++) {
let canvasBox = canvas.value!.getBoundingClientRect();
let minx = (imgList[i].x - imgWidth / 2 ) * _unit + canvas_w / 2 + canvasBox.left;
let maxx = (imgList[i].x + imgWidth / 2) * _unit + canvas_w / 2 + canvasBox.left;
let miny = (imgList[i].y - imgWidth / 2) * _unit + canvas_h / 2 + canvasBox.top ;
let maxy = (imgList[i].y + imgWidth / 2) * _unit + canvas_h / 2 + canvasBox.top;
if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy) {
canvas.value &&(canvas.value.style.cursor = 'pointer');
break
} else {
canvas.value &&(canvas.value.style.cursor = 'default');
}
}
}
鼠标在节点展示tooltip
原理和节点展示手状相同。
js
function checkMousePos(mouseX: number, mouseY: number) {
for (let i = 0 ; i < imgList.length; i++) {
//....
if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy)
{
let x = imgList[i].x + (mouseX - minx);
let y = imgList[i].y + (mouseY - miny);
drawToolTip('测试',x, y);
break
}
}
}
vue组件代码:
js
<template>
<div class="canvas" v-show="true" ref="canvasBox">
<canvas id="canvas" ref="canvas"></canvas>
</div>
</template>
ts
<script>
import { h, onMounted, onUnmounted, ref } from 'vue';
const canvas = ref<HTMLCanvasElement | null>(null);
const canvasBox = ref<HTMLElement | null>(null);
let ctx: CanvasRenderingContext2D | null;
let canvas_w: number = 0,
canvas_h: number = 0;
const imgWidth = 36;
const imgList: imgList[] = [];
function initCanvas() {
ctx = canvas.value!.getContext('2d');
setTimeout(() => {
resize();
});
window.addEventListener('resize', debounceResize);
}
const debounceResize = debounce(resize, 500)
function resize() {
canvas_w = canvasBox.value!.offsetWidth;
canvas_h = canvasBox.value!.offsetHeight;
canvas.value!.width = canvas_w;
canvas.value!.height = canvas_h;
updateCanvas();
}
let _unit = 1;
function updateCanvas() {
if (!ctx) return;
const _w = 375;
const _h = 318;
if (canvas_w / _w > canvas_h / _h) {
_unit = canvas_h / _h;
} else {
_unit = canvas_w / _w;
}
ctx.clearRect(0, 0, canvas_w, canvas_h);
ctx.translate((canvas_w) / 2, (canvas_h) / 2);
ctx.scale(_unit, _unit);
ctx && draw(ctx);
bindEvent();
}
function bindEvent() {
canvas.value!.addEventListener('mousemove',(e: any) => {
let mouseX = e.clientX;
let mousY = e.clientY;
checkMousePos(mouseX, mousY);
})
}
function checkMousePos(mouseX: number, mouseY: number) {
for (let i = 0 ; i < imgList.length; i++) {
let canvasBox = canvas.value!.getBoundingClientRect();
let minx = (imgList[i].x - imgWidth / 2 ) * _unit + canvas_w / 2 + canvasBox.left;
let maxx = (imgList[i].x + imgWidth / 2) * _unit + canvas_w / 2 + canvasBox.left;
let miny = (imgList[i].y - imgWidth / 2) * _unit + canvas_h / 2 + canvasBox.top ;
let maxy = (imgList[i].y + imgWidth / 2) * _unit + canvas_h / 2 + canvasBox.top;
if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy) {
canvas.value &&(canvas.value.style.cursor = 'pointer');
// showErrorDetails();
let x = imgList[i].x + (mouseX - minx);
let y = imgList[i].y + (mouseY - miny);
drawToolTip('测试',x, y);
break
} else {
canvas.value &&(canvas.value.style.cursor = 'default');
}
}
}
function drawToolTip(txtLoc:string, x:number, y:number) {
if (!ctx) return;
ctx.save();
var padding = 3;
var font = "16px arial";
ctx.font = font;
ctx.textBaseline = 'bottom';
ctx.fillStyle = 'yellow';
//绘制ToolTip背景
var width = ctx.measureText(txtLoc).width;
var height = parseInt(font, 10);
ctx.fillRect(x, y-height, width+padding*2, height+padding*2);
//绘制ToolTip文字
ctx.fillStyle = '#000';
ctx.fillText(txtLoc, x+padding, y+padding);
ctx.restore();
}
// function showErrorDetails() {
// isShowErrorDetails.value = true;
// }
function draw(ctx: CanvasRenderingContext2D) {
ctx.save();
ctx.fillStyle = 'rgb(16, 16, 20)';
ctx.arc(0, 0, imgWidth / 2 + 3, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
imgList.forEach((el: imgList) => {
ctx.save();
loadImage(ctx, el);
ctx.fillStyle = '#fff';
ctx.font = '11px serif';
ctx.fillText(
el.name,
el.x - ctx.measureText(el.name).width / 2,
imgWidth / 2 + el.y + 16,
);
ctx.restore();
});
}
function loadImage(ctx: CanvasRenderingContext2D, imageInfo: imgList) {
const img = new Image();
img.src = imageInfo.state ? imageInfo.src : imageInfo.src_error;
img.onload = () => {
ctx.drawImage(
img,
imageInfo.x - imgWidth / 2,
imageInfo.y - imgWidth / 2,
imgWidth,
imgWidth,
);
ctx.save();
ctx.beginPath();
if (imageInfo.id !== 'network') {
ctx.globalCompositeOperation = 'destination-over';
ctx.strokeStyle = '#fff';
if (imageInfo.state) {
ctx.strokeStyle = '#1296DB';
} else {
ctx.strokeStyle = '#DF0B01';
}
ctx.lineWidth = 2;
const info = { x: 0, y: 0 };
if (imageInfo.x > 0) {
info.x = -1;
} else {
info.x = 1;
}
if (imageInfo.y > 0) {
info.y = -1;
} else {
info.y = 1;
}
ctx.moveTo(0, 0);
ctx.lineTo(imageInfo.x, imageInfo.y + (info.y * imgWidth) / 2);
ctx.stroke();
}
ctx.restore();
};
}
onMounted(() => {
initCanvas();
});
onUnmounted(() => {
controller?.abort();
window.removeEventListener('resize', resize);
});
</script>