如何在 Canvas 上实现图形拾取? canvas数据抓取
liuian 2024-12-18 15:37 72 浏览
大家好,我是前端西瓜哥,今天来和大家说说 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 基于此去按层级绘制这些图形。
下面我们看看元素拾取的几种方案。
方案 1:isPathInPoint
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
优点:
- 原生 API 支持,方便;
缺点:
- 判断光标点中哪个元素,需要遍历元素,去调用方法,直到返回 true 为止,性能可能会差一点(可以用四叉树碰撞检测,减少需要遍历的元素数量,但极端情况可能还是会有很多元素,另外可通过包围盒减少计算量);
- 检测点是否在一条 strokeWidth 较大的线上可能会有错误,因为路径是没有宽度的;
方案 2:缓存 Canvas
根据真正的 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
优点:
- 能够快速确定点所在的图形;
- 能够修改碰撞范围,比如给一条细的线条进行区域的外扩,让用户更好选中这条线条;
- 适合图形量大、重绘较少的场景。
缺点:
- 渲染开销加倍。每个图形需要调用两次 API(页面上的 canvas 和缓存 canvas 各绘制一次);
- 如果图形频繁变化,性能会更低。
方案 3:图形学算法
可以用计算机图形学的算法,去判断一个点是否在某个形状内。
比如:
(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;
}
还有其他的:通过 “射线法” 判断点是否在多边形等。
优点:
- 某种意义上是 isPointInPath 的底层实现,能做到平台无关;
缺点:
- 和 isPointInPath 方案一样,需要遍历图形检测;
- 实现复杂,简单图形还算简单,但如果涉及到贝塞尔曲线等复杂形状,实现就会很复杂且性能堪忧(可以考虑用 isPointInPath);
- 如果使用了 transform,因为要进行矩阵乘法,性能会有所下降。
结尾
总结一下,canvas 的图形拾取有三种方案:
- isPointInPath:canvas 原生提供的 API,能够知道点是否在路径内;
- 缓存 Canvas:额外使用一个 canvas,每次绘制图形都在这个 canvas 上绘制纯色图形,记录映射关系。交互时通过 getImageData 得到颜色值,然后根据映射关系找到对应图形;
- 计算机图形学算法:自己写点是否在特定形状下的算法,本质是 isPointInPath 的底层实现。但复杂图形碰撞检测实现起来困难。
我是前端西瓜哥,欢迎关注我,学习更多知识。
相关推荐
- 10种常见的MySQL错误,你可中招?
-
【51CTO.com快译】如果未能对MySQL8进行恰当的配置,您非但可能遇到无法顺利访问、或调用MySQL的窘境,而且还可能给真实的应用生产环境带来巨大的影响。本文列举了十种MySQL...
- MySQL主从如何保证数据一致性
-
MySQL主从(主备)搭建请点击基于Spring的数据库读写分离。MySQL主备基本原理假设主备切换前,我们的主库是节点A,节点B是节点A的备库,客户端的读写都是直接访问节点A,节点B只是将A的更新同...
- MySQL低版本升级操作流程
-
(关注“数据库架构师”公众号,提升数据库技能,助力职业发展)0-升级背景MySQL5.5发布于2010年,至今已有十年历史,官方已经停止更新。2008年发布的MySQL5.1版本,在2018年...
- MySQL数据库知识
-
MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...
- Mysql 8.4数据库安装、新建用户和数据库、表单
-
1、下载MySQL数据库yuminstall-ywgetperlnet-toolslibtirpc#安装wget和perl、net-tools、libtirpcwgethtt...
- mysql8.0新功能介绍
-
MySQL8.0新特性集锦一、默认字符集由latin1变为utf8mb4在8.0版本之前,默认字符集为latin1,utf8指向的是utf8mb3,8.0版本默认字符集为utf8mb4,utf8默...
- 全网最详细解决Windows下Mysql数据库安装后忘记初始root 密码方法
-
一、准备重置root的初始化密码Win+R键启动命令输入窗口;输入cmd打开命令执行窗口;##界面如下##输入命令:netstopmysqld#此操作会停止当前运行的...
- 互联网大厂面试:MySQL使用grant授权后必须flush privilege吗
-
从我上大学时,数据库概论老师就告诉我,MySQL使用grant对用户授权之后,一定记得要用flushprivilege命令刷新缓存,这样才能使赋权命令生效。毕业工作以后,在很多的技术文档上,仍然可以...
- # mysql 8.0 版本无法使用 sqlyog 等图形界面 登录 的解决方法
-
30万以下的理想L6来了##mysql8.0版本无法使用sqlyog等图形界面登录的解决方法当我们在cmd下登录mysql时正常时,用sqlyog等图形界面连接数据库时却...
- MySQL触发器介绍
-
前言:在学习MySQL的过程中,可能你了解过触发器的概念,不清楚各位是否有详细的去学习过触发器,最近看了几篇关于触发器的文档,分享下MySQL触发器相关知识。1.触发器简介触发器即trigg...
- 管理员常用的MySQL命令汇总(一)
-
以下是管理员常用的MySQL命令:以管理员身份连接到MySQL:mysql-uroot-p创建新的MySQL用户:CREATEUSER'username'@'...
- Linux(CentOS) 在线安装MySQL8.0和其他版本,修改root密码
-
一:安装MySQL数据库1),下载并安装MySQL官方的YumRepositorymysql官方仓库地址:https://dev.mysql.com/downloads/repo/yum/选择自...
- 解决 MySQL 8.0 一直拒绝 root 登录问题
-
Accessdeniedforuser'root'@'localhost'(usingpassword:YES)这个错误在网上搜一下,能看到非常多的此类...
- 大模型MCP之MYSQL安装
-
前言学习大模型的时候需要一个mysql,原因还是在公司使用电脑的时候不允许按照Docker-Desktop,我的宿主机其实是MAC,我习惯上还是在centsos上面安装,就发现这件过去很简单的事情居然...
- MySQL ERROR 1396
-
ERROR1396(HY000):OperationCREATEUSERfailedfor'usera'@'%'问题描述mysql>create...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
飞牛OS入门安装遇到问题,如何解决?
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)