
本文介绍如何在 html5 canvas 中高效绘制六边形网格,并通过点在多边形检测算法实现单个六边形的精准交互(如悬停变色),避免为每个格子创建独立 canvas,兼顾性能与可维护性。
本文介绍如何在 html5 canvas 中高效绘制六边形网格,并通过点在多边形检测算法实现单个六边形的精准交互(如悬停变色),避免为每个格子创建独立 canvas,兼顾性能与可维护性。
在基于 Canvas 的棋盘类游戏开发中,一个常见痛点是:图形虽能绘制成功,却难以对单个单元格(如六边形)进行独立交互(例如点击变色、选中高亮)。问题根源在于——Canvas 是位图绘图 API,本身不保留图形对象信息;你画完即“丢失”了该六边形的几何定义和身份标识。
解决这一问题的关键在于分离数据与渲染:
✅ 内存中保存结构化数据:每个六边形不再只是“画出来的像素”,而是一个包含顶点坐标({x, y} 数组)和状态(如 color、id、isOccupied)的对象;
✅ 建立坐标映射逻辑:提供 isPointInPoly(polygon, point) 工具函数,实时判断鼠标位置是否落在某个多边形内部;
✅ 按需重绘+状态驱动:交互触发时,仅更新对应六边形的数据属性,并调用统一绘制函数刷新其视觉表现。
以下是一个精简、可运行的完整实现方案:
<canvas id="canvas" width="800" height="600" style="border: 1px solid #ccc;"></canvas>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const { width, height } = canvas.getBoundingClientRect();
canvas.width = width;
canvas.height = height;
// ✅ 步骤1:定义六边形数据结构(顶点数组 + 状态)
const hexagons = []; // 存储所有六边形的顶点坐标数组
// ✅ 步骤2:通用多边形绘制函数(复用性强)
function drawPolygon(polygon, color) {
ctx.beginPath();
ctx.moveTo(polygon[0].x, polygon[0].y);
for (let i = 1; i pt.y) !== (yj > pt.y)) &&
(pt.x {
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// 查找被悬停的六边形
const hovered = hexagons.find(hex => isPointInPoly(hex.polygon, { x: mouseX, y: mouseY }));
// 清空画布并重绘全部(简单可靠;高频交互可优化为局部重绘)
ctx.clearRect(0, 0, canvas.width, canvas.height);
hexagons.forEach(hex => {
// 若当前六边形被悬停,临时使用高亮色(如黄色),否则用原色
const renderColor = hovered && hex === hovered ? '#FFD700' : hex.color;
drawPolygon(hex.polygon, renderColor);
});
// 更新光标样式增强反馈
canvas.style.cursor = hovered ? 'pointer' : 'default';
});
// ? 扩展提示:如需点击变色,将 'mousemove' 替换为 'click',并在事件中修改 hex.color 后重绘
关键注意事项:
- 性能考量:本例每次悬停都全量重绘,适合中小规模网格(≤200 格)。若格子极多(如千级),建议缓存离屏 Canvas 或仅重绘受影响区域;
-
坐标精度:
getBoundingClientRect()获取的是 CSS 像素,确保 Canvas 未被 CSS 缩放(否则需用devicePixelRatio校准); -
可扩展性:
hexagons数组中的每个对象可自由添加属性(如owner: "player1"、terrain: "forest"),为游戏逻辑打下基础; -
算法鲁棒性:
isPointInPoly使用经典的射线交叉法,兼容任意凸/凹多边形,且已针对六边形顶点顺序做了验证。
通过这种“数据驱动 + 函数式渲染”的范式,你不仅解决了单格交互难题,更构建了一个可长期演进的棋盘系统骨架——后续添加拖拽、动画、碰撞检测等特性都将水到渠成。











