canvas 元素拾取

canvas isPointInPath/isPointInStroke 元素拾取

背景

在 2D 画布应用中,需要判断用户点击/悬停的是哪一个可交互元素。常见解决方案包括:

方案 优点 缺点
isPointInPath 实现成本低, 系统提供默认方法, 性能优越 一般需要配合其他方法处理 (BVH 做初筛), 对于大批量渲染场景处理性能不够友好 , 对于不规则图片难以处理
colorPick 选取快速, 对于渲染大量不规则图形,需要频繁拾取的场景性能优异 需要额外的渲染成本

关于 colorPick 可以参考我的另外一篇文章 colorPick

canvas 元素 拾取的大致流程

拾取初筛

为什么需要做一个初筛?

  • 过滤掉大部分不可能命中的元素
  • 图形可能不规整, 凹多边形/凸多边形, 判断是否命中的逻辑过于复杂, 用简单检测代替复杂检测

BVH 介绍 qTree 四叉树介绍

如何做初筛?

  • 过滤掉不可能命中的元素 -> BVH/qTree 这是两种比较常见的方法过滤不会命中的元素
  • 采用包围盒做一个命中的初筛
    • 2d 场景中 AABB 包围盒(轴对齐包围盒), 在 3d 场景中经常使用球体. 也有一些其他包围盒.
    • AABB包围盒比较容易获取, 并且计算简单, 3d 场景中球体最容易计算 (只需要判断点和目标圆心的距离既可)
    • 包围盒本身和视觉实体存在误差
    • 包围盒只是用来做初步检测的得到 没有命中/可能命中 两个答案, 没有命中 自然直接结束 可能命中 则需要进一步处理
    • 也存在某些非高精度要求场景直接使用包围盒替代命中检测的情况

大致流程图

非高精度场景示例图(3d 场景中常见)

在图片上可以看到,当采用凸多边形包围盒在某些场景中, 可以替代精准的拾取计算, 特别是在 3d 场景中.

对于线段/绘制的拾取

用户的绘制其实是一系列点的集合,如何拾取一条线段可以使用 isPointInPath 来拾取, 但是单点拾取难度高, 并且用户绘制时可能设置了画笔粗细, 另外可能有光滑曲线设置 -> 连续的贝塞尔曲线转换

可以参考的方法

  • 依旧使用 isPointInPath , 而不是 isPointInStroke. 但是将每个点的纵坐标(y) +- 1,做一遍拾取, 也就是拾取三条线段组合成一条线段的拾取, 更加符合用户逻辑
  • 根据 path 和 stroke 的宽度, 手动给线段构建一个 stroke 的实体, 作为一个区域, 通过 isPointInStroke 来拾取.

对于不规则图片的拾取处理

  • 采用 colorPick 方法, 一劳永逸的解决问题, 代价是 offsetScreenCanvas 重新渲染一遍
  • 手动给图片构建 外部的粗略包围盒 path, 再通过 isPointInStroke 判断拾取, 具体算法可以去检索一下
  • 偷个懒,直接用图片的 AABB 包围盒 (也就是图片的实际大小) 代替图片内容做拾取

总结

canvas 元素拾取需要的方法 初筛工具 -> BVH, qTree 等 精准检测工具 isPointInPath, isPointInStroke

对于某些不规则图形来说, 需要手动构建对应的 path , 或者采用 colorPick 方案, 或者采用近似解.

相关推荐
从文处安6 小时前
「前端何去何从」React Router:让单页应用有多页的体验
前端·react.js
Lkstar6 小时前
Vue Router 进阶:导航守卫、动态路由与懒加载,源码级理解
前端
ricardo19736 小时前
# Tree Shaking 深度解析:为什么你的代码没被摇掉?
前端·面试
前端流一6 小时前
踩坑实录:Vite打包AntD5报错 rc-picker/es/generate/dayjs 模块找不到
前端
_按键伤人_6 小时前
三、手把手教你从零写一个本地 RAG
前端·llm·ai编程
008爬虫实战录6 小时前
【码上爬】 题十二:如来神掌 困难, JSVMP加密,使用代理补环境
前端·javascript·node.js
008爬虫实战录6 小时前
【码上爬】 题十:魔改算法 堆栈分析,找加密值过程详解
前端·python·算法
无人装备硬件开发爱好者6 小时前
深度解析GPS天线设计:从贴片天线到LNA前端的完整硬件方案
前端
卷帘依旧7 小时前
React Hook采用环形链表的原因
前端