canvas坐标系统默认为左上角为原点,横轴x轴,向右为正,纵轴为y轴,向下为正的窗口坐标系统,可以对坐标系统坐标变换,平移、缩放、旋转、自定义变换方式等。
mousemove、mouseup和mousedown等事件获取到的是默认窗口坐标系统的事件位置。在获取绘画内容的位置时,需要转换后的坐标系统和默认窗口坐标系统转换计算。
canvas对于画笔样式使用save,restore栈的方式记录。
坐标变换
移动
translate(x, y),画布沿着X轴方向和Y轴方向进行平移。
下图,有一个点在原坐标系的坐标是(20,10)。
translate(6, 5),坐标系变换,原点(画布)向右平移6,向下平移5。此时得到圆点在原始坐标系位置为(20+6, 10+5)即(26, 15)。
缩放
scale(x, y),画布沿着X轴方向和Y轴方向进行比例缩放。 scale(0.5, 0.5),坐标系在平移基础上再次进行变换,画布在x轴和y轴上缩小0.5倍。圆点到变换后的坐标系的实际坐标为(10, 5)。此时,圆点在最初始的坐标系的位置为(10+6, 5+5),即(16, 10)。
事件event位置
canvas事件存储的是相对于最原始坐标系的位置。在需要判断事件是否在画布上的某个位置时,需要把转换坐标系中的坐标转换成原始坐标系的坐标。
上面的实例,先translate(6, 5),在scale(0.5, 0.5)。圆点(x, y)(即(20, 10))在最原始坐标系的坐标是:
x' = 6 + (x* 0.5),
y' = 5 + (y* 0.5)。
clientX表示事件到窗口的x轴距离,react.left表示canvas容器到窗口的距离,posX(react.left - e.clientX)则表示事件在坐标系上x轴上的距离。
canvas.addEventListener('mousemove',(e: any) => {
// canvas容器相对于窗口的位置
let react = canvas.getBoundingClientRect();
//e.clientX相对于窗口的位置,canvasBox.left canvas容器相对于窗口的左侧距离,mouseX鼠标在原坐标系的位置
let x = e.clientX - react.left;
let y = e.clientY - react.top;
console.log('鼠标在原坐标系x轴得位置':x);
console.log('鼠标在原坐标系y轴得位置':y);
})
如果click的位置(posX, posY)在(x', y')上,点击位置在圆点上。
拓补图支持适配
通过初始化一个_w和_h的值,_w = 375, _h = 318,_unit = 1。监听页面的大小canvas的大小跟随变动,计算_unit的值。
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的值,与节点在坐标轴上的转换值对比。
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
原理和节点展示手状相同。
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
}
}
}