
本文介绍如何在 html5 canvas 中高效绘制六边形网格,并通过点选检测实现单个六边形的独立交互(如点击变色),避免为每个六边形创建独立 canvas,核心在于内存中存储几何数据 + 射线法点内检测。
本文介绍如何在 html5 canvas 中高效绘制六边形网格,并通过点选检测实现单个六边形的独立交互(如点击变色),避免为每个六边形创建独立 canvas,核心在于内存中存储几何数据 + 射线法点内检测。
在基于 Canvas 的棋盘类游戏开发中,一个常见误区是试图为每个可交互单元(如六边形格子)创建独立的 <canvas></canvas> 元素或上下文——这不仅性能极差,也违背 Canvas 的设计初衷。真正高效且可扩展的方案是:将图形逻辑与渲染逻辑分离——在内存中用纯数据结构(如坐标数组)描述每个六边形,再通过数学方法判断鼠标是否落在其内部,最后仅重绘目标区域。
✅ 正确的数据建模方式
不再使用 tuiles[x+' '+y] = canvas.getContext("2d") 这类错误引用(Canvas 2D 上下文不可重复赋值,且无法作为“对象容器”),而是统一使用一个全局 ctx,并将每个六边形定义为顶点坐标数组:
const hexagons = []; // 存储所有六边形的顶点集合(每个元素是6+1个{x,y}对象的数组)
function hexa(x, y, s) {
const polygon = [
{ x, y },
{ x: x + s/2, y: y - s/5 },
{ x: x + s, y },
{ x: x + s, y: y + s/2 },
{ x: x + s/2, y: y + s/2 + s/5 },
{ x, y: y + s/2 },
{ x, y } // 闭合起点
];
hexagons.push(polygon);
drawPolygon(polygon, randomColor());
}
该结构清晰表达了“每个六边形是什么”,而非“它画在哪”——为后续交互打下坚实基础。
✅ 点击检测:判断鼠标是否在六边形内
Canvas 本身不提供内置的形状命中检测,需手动实现。这里采用经典的 射线交叉法(Ray Casting Algorithm),适用于任意简单多边形(包括凸/凹):
function isPointInPoly(poly, pt) {
let inside = false;
for (let i = 0, j = poly.length - 1; i pt.y) !== (yj > pt.y)) &&
(pt.x <p>该函数接收一个多边形顶点数组和一个点坐标,返回布尔值,精度高、性能好(O(n)),是生产环境推荐方案。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5640" title="Canva Skill"><img
src="https://img.php.cn/upload/skill/000/000/081/179058611646848.jpg" alt="Canva Skill" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill5640" title="Canva Skill" class="overflowclass">Canva Skill</a>
<p class="overflowclass">通过 Canva Connect API 创建、导出、上传和管理 Canva 设计。用于创建社交媒体帖子、海报、PPT/幻灯片等视觉内容。</p>
</div>
<a rel="nofollow" href="/xiazai/skill5640" title="Canva Skill" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h3>✅ 实现交互:监听鼠标并重绘目标格子</h3><p>为 <code><canvas></canvas></code> 绑定 <code>click</code>(或 <code>mousemove</code>)事件,获取相对于画布的坐标(注意使用 <code>offsetX</code>/<code>offsetY</code> 而非 <code>clientX</code>),遍历 <code>hexagons</code> 查找命中的六边形,并调用 <code>drawPolygon()</code> 重绘其新颜色:</p><pre class="brush:php;toolbar:false;">canvas.addEventListener('click', function(ev) {
const rect = canvas.getBoundingClientRect();
const x = ev.clientX - rect.left;
const y = ev.clientY - rect.top;
const targetHex = hexagons.find(hex => isPointInPoly(hex, { x, y }));
if (targetHex) {
// 可在此处添加业务逻辑,如记录选中状态、触发游戏规则等
drawPolygon(targetHex, randomColor()); // 即时变色
}
});⚠️ 注意事项:
- 性能优化:若格子数量极大(>1000),建议结合空间索引(如四叉树)加速查找;
- 重绘范围:当前示例直接重绘整个六边形,若需保留背景或叠加效果,应先清除原区域(
ctx.clearRect())再重绘;- 响应式适配:
getBoundingClientRect()确保坐标与 CSS 缩放/滚动兼容,优于offsetX/Y(后者在某些浏览器中可能失效);- 颜色管理:可将
color字段存入hexagons[i]对象中(如polygon.color = "#ff0000"),便于状态持久化与批量操作。
✅ 总结:Canvas 交互设计黄金法则
| 原则 | 说明 |
|---|---|
| 数据驱动渲染 | 所有可交互对象必须有对应内存数据模型(坐标、状态、ID等),而非依赖 DOM 或 canvas 上下文引用 |
| 分离关注点 | 绘制(drawPolygon)、检测(isPointInPoly)、响应(事件回调)三者解耦,便于测试与复用 |
| 最小化重绘 | 仅重绘被交互影响的局部区域,避免 clearRect(0,0,w,h) 全屏擦除(尤其在动画场景中) |
| 坐标归一化 | 始终使用相对于 canvas 元素自身的像素坐标,规避 CSS transform / viewport 缩放干扰 |
通过以上结构,你不仅能轻松实现“点击变色”,还可进一步扩展为:拖拽移动、悬停高亮、路径寻址、碰撞判定等复杂交互,真正构建出高性能、可维护的 Canvas 游戏底层框架。









