前端实现蜂巢布局思路

效果图如下

上图的组成可以分为两部分,一个为底图六边形的形成,一个为内容六边形的形成。

要生成对应的六边形,首先要获得需要绘制的六边形的中心坐标。

观察不难得出结论,以中心的六边形为基点,第二圈很明显能排6个,第三圈能排12个,以此进行类推。

这里可以以中心点为坐标原点0,0,以1,0, 1,1,-1,1,-1,0,-1,-1,1,-1这种形式来表示第二圈在轴线上的六个六边形的中心点关系(这是一种形式,并非真实的坐标系坐标)。

js 复制代码
// 第二圈时的对应圆上的坐标值
const pixiArr = [
  [1, 0],
  [1, 1],
  [-1, 1],
  [-1, 0],
  [-1, -1],
  [1, -1]
]

// 根据圈数生成对应轴线上的坐标
function generatePixiArrByWeight(weight) {
  if (weight === 2) {
    return pixiArr
  }
  const multiple = weight - 1
  const tempPixiArr = pixiArr.map((x) => {
    return [x[0] * multiple, x[1] * multiple]
  })
  return tempPixiArr
}

进一步观察,可知,第三圈时两条发散的轴中间夹了一个六边形,第四圈时两条发散的轴中间夹了两个六边形,依次类推。

六条发散轴上的六边形中心点坐标是最容易计算的,不过要计算三圈及其开外的,就得有那么一点点的数学基础,知道sin60度cos60度的意思。

js 复制代码
const sin60 = Math.sin(Math.PI / 3)
const cos60 = Math.cos(Math.PI / 3)

有了上面的铺垫后就可以开始了,定义一个函数,传入的参数为六边形总个数和六边形的边长

js 复制代码
// 生成六边形中心坐标
function getHexagonCoordinateArrayByTotal(total = 0, radius = 0){
    // 1、获取圈数weight
    if (total === 0) return []
  let tierList = [] // 用于存放每圈的个数
  let tierIndex = 0
  while (total > 0) {
    if (tierIndex === 0) {
      tierList.push(1)
      total = total - 1
    } else {
      let n = 6 * tierIndex
      total = total - n
      if (total < 0) {
        tierList.push(total + n)
      } else {
        tierList.push(n)
      }
    }
    tierIndex++
  }
  const weight = tierList.length
    // 2、根据圈数去获取coordinateArray坐标列表
    // getHexagonCoordinateArrayByWeight:根据圈数和边长返回对应的坐标点
    const weight = tierList.length
  let coordinateArray = []
  for (let i = 0; i < weight; i++) {
    if (i + 1 === weight) {
      coordinateArray = [
        ...coordinateArray,
        ...getHexagonCoordinateArrayByWeight(i + 1, radius).slice(
          0,
          tierList[weight - 1]
        )
      ]
    } else {
      coordinateArray = [
        ...coordinateArray,
        ...getHexagonCoordinateArrayByWeight(i + 1, radius)
      ]
    }
  }

  return coordinateArray
}

有个getHexagonCoordinateArrayByWeight需要实现其,方式为

js 复制代码
function _abs(val = 0) {
  return Math.abs(val)
}

function getHexagonCoordinateArrayByWeight(weight = 1, radius = 0) {
  if (weight === 0) return []
  if (weight === 1) return [[0, 0]]
  const addNum = weight - 2
  const addArr = generatePixiArrByWeight(weight)
  const hypotenuse = radius * sin60 * 2 // 两倍的边心距长度
  let offsetArr = []
  let offsetX
  let offsetY
  for (let i = 0; i < addArr.length; i++) {
    const t = addArr[i]
    if (t[1] !== 0) {
      offsetX = t[0] * hypotenuse * cos60
      offsetY = t[1] * hypotenuse * sin60
    } else {
      offsetX = t[0] * hypotenuse
      offsetY = 0
    }
    offsetArr.push([offsetX, offsetY])
  }
  const tempOffsetArr = JSON.parse(JSON.stringify(offsetArr))
  let resArr = new Array(6 * (weight - 1))
  let lineArr = []
  for (let i = 0; i < 6; i++) {
    let lindex = i * (weight - 1)
    resArr[lindex] = tempOffsetArr[i]
    lineArr.push(lindex)
  }
  // 利用已知的六个发散轴上的中心坐标点推出剩余的中心坐标点
  if (addNum > 0) {
    for (let i = 0; i < 6; i++) {
      let s = tempOffsetArr[i]
      let e = i + 1 === 6 ? tempOffsetArr[0] : tempOffsetArr[i + 1]
      let si = lineArr[i]
      let sp = addNum + 1
      let fx
      let fy
      if (i === 0) {
        fx = (s[0] - e[0]) / sp
        fy = (e[1] - s[1]) / sp
      }
      if (i === 1) {
        fx = (_abs(s[0]) + _abs(e[0])) / sp
        fy = 0

      }
      if (i === 2) {
        fx = (_abs(e[0]) - _abs(s[0])) / sp
        fy = (_abs(s[1]) - _abs(e[1])) / sp
      }
      if (i === 3) {
        fx = (_abs(s[0]) - _abs(e[0])) / sp
        fy = (_abs(e[1]) - _abs(s[1])) / sp
      }
      if (i === 4) {
        fx = (_abs(s[0]) + _abs(e[0])) / sp
        fy = 0
      }
      if (i === 5) {
        fx = _abs(s[0]) / sp
        fy = (_abs(e[1]) - _abs(s[1])) / sp
      }
      let mr = []
      for (let j = 0; j < addNum; j++) {
        if (i === 0 || i === 1) {
          mr.push([s[0] - fx * (j + 1), s[1] + fy * (j + 1)])
        }
        if (i === 2) {
          mr.push([s[0] - fx * (j + 1), s[1] - fy * (j + 1)])
        }
        if (i === 3) {
          mr.push([s[0] + fx * (j + 1), s[1] - fy * (j + 1)])
        }
        if (i === 4) {
          mr.push([s[0] + fx * (j + 1), s[1] - fy * (j + 1)])
        }
        if (i === 5) {
          mr.push([s[0] + fx * (j + 1), s[1] - fy * (j + 1)])
        }
      }
      mr.forEach((x, index) => {
        resArr[si + index + 1] = x
      })
    }
  }
  return resArr
}

至此,生成六边形中心坐标点的方法完成。 有了中心坐标生成方式之后,就可以使用Konva这种辅助绘图的库来进行效果绘制了。

相关推荐
feixing_fx4 分钟前
伪类与伪元素的魔法:纯 CSS 打造炫酷的按钮悬停特效
前端·css·css3
动恰客流统计4 分钟前
用客流数据优化门店排班:高峰不缺人、平峰不浪费的落地路径
大数据·前端·人工智能
广州山泉婚姻14 分钟前
前后端分离的核心优势
前端·后端
志尊宝14 分钟前
Vue3 零基础每日笔记(043):defineModel 与 defineExpose 的 TS 用法——3.4+ 新 API 进阶
前端·javascript·vue·html·vue3
洛兮银儿16 分钟前
前端页面引入cesium
前端·cesium
SEO_juper26 分钟前
2026年用Python分析网站访问日志:看清Googlebot和AI爬虫怎么爬你的站(附完整代码)
开发语言·前端·seo·独立站·谷歌优化
摸鱼仙人~28 分钟前
React 原理进阶:彻底理解 re-render、React.memo、useMemo 与 useCallback
前端·vue.js
IMPYLH32 分钟前
HTML 的 <small> 元素
前端·网络·html
IT_陈寒33 分钟前
Vue的响应式让我加班到凌晨,问题竟出在这个不起眼的地方
前端·人工智能·后端
AlienZHOU9 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试