火次果
火次果

暂无菜单项

首页/文章/技术文章/web前端/HTML5/
打开 MD 链接

Canvas 六边形棋盘:点击着色的做法

发布于 3天前
2

棋盘类界面里,给每个格子建一个 canvas 是最容易想到的做法,代价是节点数暴涨、事件要逐个绑定。单个 canvas 承载整盘,靠坐标换算定位格子,节点只剩一个。

屏幕上的代码与反光,用于说明 Canvas 绘制与命中判定
整盘绘制加一次命中判定,比逐格建节点更轻

布局计算

const R = 24;                    // 外接圆半径
const W = Math.sqrt(3) * R;      // 横向间距
const H = 2 * R;                 // 格高
const rowGap = H * 0.75;         // 纵向间距

function center(col, row) {
  const x = W * (col + (row % 2 ? 0.5 : 0));
  const y = rowGap * row;
  return { x, y };
}

尖顶六边形按行交错排布,奇数行整体右移半个格宽。绘制时按中心点依次连出六个顶点即可。

命中检测

function pointInPolygon(px, py, pts) {
  let inside = false;
  for (let i = 0, j = pts.length - 1; i < pts.length; j = i++) {
    const [xi, yi] = pts[i];
    const [xj, yj] = pts[j];
    if ((yi > py) !== (yj > py) &&
        px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) {
      inside = !inside;
    }
  }
  return inside;
}

射线法对任意多边形都成立。棋盘规模大时先按行列粗筛,只对候选格子做多边形判定,判定次数大幅下降。

三个要点

  1. 坐标换算要减去画布偏移。事件坐标减去 getBoundingClientRect() 的结果,得到画布内坐标。
  2. 按 devicePixelRatio 放大位图。CSS 尺寸与位图尺寸分开设置,再用 scale 校正,否则边缘发虚。
  3. 重绘只清一次画布。状态变更后整体重绘,增量擦除带来的残影比全量重绘更难处理。

交互层

click 处理选中与着色,mousemove 处理悬停高亮,两者复用同一套命中逻辑。触屏环境补充 touchstart 的坐标读取,换算方式一致。

常见问题(FAQ)

每个格子用一个 canvas 可以吗?
可以实现,节点数与事件绑定数量随格数线性增长,重绘与内存开销都更高。单画布绘制整盘更轻。
点击坐标对不上格子怎么办?
事件坐标要减去画布在页面中的偏移量,即 getBoundingClientRect 的结果;CSS 缩放与 devicePixelRatio 也要一并校正。
六边形间距怎么算?
尖顶六边形的横向间距为外接圆半径的根号三倍,纵向间距为半径的一点五倍,奇数行整体右移半个横向间距。
棋盘很大时判定会不会慢?
先按行列粗筛出候选格子,再对少数候选做多边形判定。逐格判定在格数上万时才成为瓶颈。
画布上有锯齿怎么处理?
按 devicePixelRatio 放大位图尺寸,再用 scale 校正绘制坐标,最后用 CSS 把显示尺寸设回原始大小。
Canvas事件监听命中检测图形绘制网页性能
支持作者
如果这篇内容对你有帮助,可以请作者喝杯咖啡
0 点赞
0 收藏
分享
0 讨论
反馈
0 / 600
细中粗
0 讨论
热门最新
总结
暂无总结
嗨,下午好!
所有的成功,都源自一个勇敢的开始
创作
社区
购物
会员
近期热门

暂无数据

火次果
火次果
首页
资迅中心
小店
AI导航
社区
所有的成功,都源自一个勇敢的开始
不辜负每一个勇敢的开始
关于FAQ协议
火次果 © 2026鲁ICP备2025164830号-1