canvas isPointInPath/isPointInStroke 元素拾取
背景
在 2D 画布应用中,需要判断用户点击/悬停的是哪一个可交互元素。常见解决方案包括:
| 方案 | 优点 | 缺点 |
|---|---|---|
isPointInPath |
实现成本低, 系统提供默认方法, 性能优越 | 一般需要配合其他方法处理 (BVH 做初筛), 对于大批量渲染场景处理性能不够友好 , 对于不规则图片难以处理 |
colorPick |
选取快速, 对于渲染大量不规则图形,需要频繁拾取的场景性能优异 | 需要额外的渲染成本 |
关于 colorPick 可以参考我的另外一篇文章 colorPick
canvas 元素 拾取的大致流程
拾取初筛
为什么需要做一个初筛?
- 过滤掉大部分不可能命中的元素
- 图形可能不规整, 凹多边形/凸多边形, 判断是否命中的逻辑过于复杂, 用简单检测代替复杂检测
如何做初筛?
- 过滤掉不可能命中的元素 -> 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 方案, 或者采用近似解.