如何在HTML/JavaScript中绘制居中六边形数点阵?
如何绘制居中六边形数对应的点阵
核心问题分析
需要绘制第18个居中六边形数的点阵(共919个点),要求适配指定尺寸的正方形画布,且点之间各方向间距满足最小要求(比如8px)。当前核心卡点是通过三角函数计算每个点的坐标,以及根据画布尺寸确定合适的点间距。
数学逻辑与坐标计算
正六边形的6条边对应6个固定方向,角度分别为0°、60°、120°、180°、240°、300°,转换成JS中Math.sin/Math.cos所需的弧度,对应0, π/3, 2π/3, π, 4π/3, 5π/3。
针对居中六边形点阵:
步长计算:设点半径为
MIN_RADIUS,点间最小间隙为IDEAL_GAP,则相邻点的圆心间距至少为2 * MIN_RADIUS + IDEAL_GAP(记为MIN_STEP)。同时要确保整个六边形能放进画布:- 六边形总宽度 =
2 * MAX_DEPTH * 步长(最左到最右点的距离) - 六边形总高度 =
MAX_DEPTH * 步长 * Math.sqrt(3)(正六边形高度为√3倍边长) - 取画布宽高中的较小值反推最大可用步长,再和
MIN_STEP比较,取较小值作为最终步长STEP。
- 六边形总宽度 =
点坐标计算:
- 中心坐标为画布中点:
centerX = canvas.width / 2,centerY = canvas.height / 2,先绘制中心的1个点。 - 每个深度
d(从1到MAX_DEPTH)对应一个壳层,每条边有d个点,共6*d个点。通过三角函数计算每条边的顶点坐标,再用线性插值得到边上的中间点。
- 中心坐标为画布中点:
修正后的代码实现
const canvas = document.querySelector('#canvas') const context = canvas.getContext('2d') const canvasWidth = canvas.width const canvasHeight = canvas.height const MAX_DEPTH = 18 const MIN_RADIUS = 4 const IDEAL_GAP = 4 const MIN_STEP = 2 * MIN_RADIUS + IDEAL_GAP // 点之间的最小圆心间距 // 计算最大可用步长,确保六边形能完整放入画布 const maxStepByWidth = canvasWidth / (2 * MAX_DEPTH) const maxStepByHeight = canvasHeight / (MAX_DEPTH * Math.sqrt(3)) const STEP = Math.min(maxStepByWidth, maxStepByHeight, MIN_STEP) const COLOR = ['red', 'orange', 'yellow', 'green', 'blue', 'violet'] const centerX = canvasWidth / 2 const centerY = canvasHeight / 2 // 绘制中心的点 drawCircle({ cx: centerX, cy: centerY, r: MIN_RADIUS, depth: 0 }) // 绘制每个深度的壳层 for (let d = 1; d <= MAX_DEPTH; d++) { // 遍历6条边 for (let side = 0; side < 6; side++) { // 当前边的起始/结束角度(弧度) const startAngle = side * Math.PI / 3 const endAngle = (side + 1) * Math.PI / 3 // 计算当前边的两个端点坐标(深度d的顶点) const startX = centerX + d * STEP * Math.cos(startAngle) const startY = centerY + d * STEP * Math.sin(startAngle) const endX = centerX + d * STEP * Math.cos(endAngle) const endY = centerY + d * STEP * Math.sin(endAngle) // 绘制这条边上的d个点(含起始端点,不含结束端点,避免重复) for (let i = 0; i < d; i++) { // 线性插值计算点坐标 const ratio = d === 1 ? 0 : i / (d - 1) const cx = startX + (endX - startX) * ratio const cy = startY + (endY - startY) * ratio drawCircle({ cx, cy, r: MIN_RADIUS, depth: d }) } } } function drawCircle({ cx, cy, r, depth }) { context.beginPath() context.arc(cx, cy, r, 0, 2 * Math.PI, false) context.fillStyle = COLOR[(depth - 1) % COLOR.length] ?? 'black' context.fill() }
<h1>Canvas below</h1> <canvas id="canvas" width="800" height="800"></canvas> <h1>Canvas above</h1>
代码说明
- 步长计算:优先保证点间距满足最小要求,同时确保六边形能完整放入画布,取两者中的较小值作为最终步长。
- 中心绘制:先绘制最中心的1个点,对应居中六边形数的起始点。
- 壳层绘制:
- 对每个深度
d,遍历6条边,每条边对应60°的角度区间。 - 通过线性插值计算每条边上的
d个点,避免顶点重复绘制(每条边只包含起始端点,结束端点由下一条边绘制)。 - 用三角函数计算顶点坐标,再通过比例插值得到边上的中间点。
- 对每个深度
拓展到其他多边形/星形数的思路
- 中心多边形数:比如中心五边形、七边形,只需改变边数
m,角度间隔改为2π/m,每个深度d的壳层点数为m*d,按同样的线性插值逻辑绘制。 - 多边形数:这类数从单个顶点向外扩展,需根据不同类型调整坐标逻辑(比如三角形数按行绘制,正方形数按网格排列),核心是找到点阵的排列规律再做坐标转换。
- 星形数:先确定星形的角度间隔(比如五角星为72°),再计算顶点和边上点的坐标,本质仍是三角函数的应用。
内容的提问来源于stack exchange,提问作者Lance Pollard
相关产品推荐
相关产品推荐

