注册
web

前端实现蜂巢布局思路

效果图如下


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


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


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


image.png


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


// 第二圈时的对应圆上的坐标值
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度的意思。


const sin60 = Math.sin(Math.PI / 3)
const cos60 = Math.cos(Math.PI / 3)

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


// 生成六边形中心坐标
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需要实现其,方式为


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这种辅助绘图的库来进行效果绘制了。


作者:前端_六一
来源:juejin.cn/post/7291125785796018236

0 个评论

要回复文章请先登录注册