大家好,我是前端西瓜哥,今天来和大家说说 canvas 怎么做图形拾取。
图形拾取,指的是用户通过鼠标或手指在图形界面上能选中图形的能力。图形拾取技术是之后的高亮图形、拖拽图形、点击触发事件的基础。
canvas 作为一个过于朴实无华的绘制工具,我们想知道如何让 canvas 能像 HTML 一样,知道鼠标点中了哪个 “div”。
canvas 只提供 API 在画布上绘制形状,并不知道它之前画过的图形是什么,不会保存它们的坐标、宽高等信息。
所以如果你想让 canvas 支持将其中的图形进行编辑,比如拖拽和放大,那就必须自己去维护一棵节点树。
类似这样:
const tree = {
type: 'stage',
children: [
{
type: 'rect',
x: 10, y: 10, w: 100, h: 100,
fill: 'red',
},
{
type: 'circle',
x: 0, y: 0, radius: 80,
stroke: 'yellow',
}
],
};
然后 canvas 基于此去按层级绘制这些图形。
下面我们看看元素拾取的几种方案。
isPointInPath 是 canvas 原生提供的一个检测某个点是否在指定路径内的方法。
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.beginPath(); // 表示路径的开始
ctx.rect(30, 30, 100, 50);
ctx.stroke(); // 如果只是计算,可以不绘制出来
ctx.isPointInPath(40, 40); // true,在路径内
ctx.isPointInPath(10, 10); // false,不在路径内
线上 demo:
https:/ /codesandbox.io/s/h7pxsm
优点:
缺点:
根据真正的 canvas 元素,额外创建一个大小相同离屏的缓存 canvas 元素。
每次我们在主 canvas 上绘制形状时,也在缓存 canvas 上绘制同样形状的纯色块,并用哈希表记录颜色和对应的图形对象,比如红色表示矩形 A,绿色表示矩形 B。
然后当我们在真实 canvas 上点击时,我们在 canvas 绑定事件,就可以拿到坐标位置 (x, y)
,再通过 offScreenCtx.getImageData(x, y, 1, 1)
方法得到缓存 canvas 的对应像素点的颜色值,然后找到它对应的图形对象,执行其注册的事件。
Konva 库使用了该方案。
写了个简单的线上 demo,你可以尝试点击上面那个 canvas 下的图形,看看控制台输出:
https:/ /codesandbox.io/s/veivt3
优点:
缺点:
可以用计算机图形学的算法,去判断一个点是否在某个形状内。
比如:
(1)点是否在矩形内。
function isPointInRect(point, rect) {
return (
point.x >= rect.x &&
point.y >= rect.y &&
point.x <= rect.x + rect.width &&
point.y <= rect.y + rect.height
);
}
(2)点是否在圆形内。
export function isPointInCircle(point, circle) {
const dx = point.x - circle.x;
const dy = point.y - circle.y;
const dSquare = dx * dx + dy * dy;
return dSquare <= circle.radius * circle.radius;
}
还有其他的:通过 “射线法” 判断点是否在多边形等。
优点:
缺点:
总结一下,canvas 的图形拾取有三种方案:
getImageData
得到颜色值,然后根据映射关系找到对应图形;我是前端西瓜哥,欢迎关注我,学习更多知识。