天幕:六边形特效

lxf2023-03-18 18:50:01

如题,我们将主要用 Canvas 来实现一个六边形布满天空的效果,如下:

天幕:六边形特效

前言

最近《三体》比较火热,本效果的创作灵感来自其片尾:

智子 2 号二维已经成功展开,将对人类进行实时监控

天幕:六边形特效

功能有:

  1. 绘制六边形
  2. 六边形效果
  3. 鼠标交互效果

其中,第 1 点是重点,详细讲解。

绘制六边形

绘制六边形,思路如下:

1. 找到六边形的点

我们使用到三角形的知识点 - 正弦(sine)sin(θ), 余弦(cosine)con(θ) 求距离。

天幕:六边形特效

应用到六边形上,我们以六边形的中心为圆心画圆,就可以很直观得观察到,如下:

天幕:六边形特效

假设我们设置圆心坐标为 (0, 0),圆的半径为 r,那么我们将得到右下角的点坐标为 (cos(360 / 6 / 2 deg) * r, sin(360 / 6 / 2 deg) * r)。同理,我们可以得到其他 5 个点的的坐标。相关代码如下:

/*
* x, y 为原点坐标
* r 为圆的半径
*/
function locate(x, y, r) {
  // locate hexagon point
  for(let i = 0; i < 6; i += 1) {
    particlePosition.push({
      x: x + Math.cos(Math.PI / 6 * (1 + 2 * i))*r,
      y: y + Math.sin(Math.PI / 6 * (1 + 2 * i))*r
    })
  }
}

2. 将点连线

我们定位到六边形的点之后,遍历这些点,将两点距离大于等于 r - 1 且小于等于 r + 1 的点连接起来。

for(let i = 0; i < particlePosition.length; i += 1) {
  for(let j = 0; j < particlePosition.length; j += 1) {
    let dx = particlePosition[i].x - particlePosition[j].x;
    let dy = particlePosition[i].y - particlePosition[j].y;
    let distance = Math.sqrt(dx * dx + dy * dy);
    
    if(distance >= (radius - 1) && distance <= (radius + 1)) {
      // connect points
    }
  }
}

这里的判断规则为 distance >= (radius - 1) && distance <= (radius + 1),读者可以自行更改。笔者这里以 1 为偏偏移值,是因为计算出来的两点距离不绝对等于 radius 值。

连线之后,效果如下图:

天幕:六边形特效

六边形效果

细心的读者,看到片头的 GIF 图就会发现六边形上的线条效果和六边形图片效果。

线条效果

这里使用的是 canvas 的线性渐变函数 createLinearGradient 来实现:

let randomArr = [Math.random(), Math.random(), Math.random()];
randomArr.sort(function(a,b){
  return a-b;
});
gradient = context.createLinearGradient(particlePosition[i].x, particlePosition[i].y, particlePosition[j].x, particlePosition[j].y);
gradient.addColorStop(randomArr[0], 'red');
gradient.addColorStop(randomArr[1], 'fuchsia');
gradient.addColorStop(randomArr[2], 'purple');
context.strokeStyle = gradient;

context.lineWidth = 1;
context.beginPath();
context.moveTo(particlePosition[i].x, particlePosition[i].y);
context.lineTo(particlePosition[j].x, particlePosition[j].y);
context.stroke();

六边形图片效果

六边形图片效果,本来是用 canvasclip 这个 api 去实现的,但是发现在本案例实现起来,翻车了,页面卡死了,故选择了操作 img 节点结合 css 来实现:

<img id="img" src="" alt="img"/>
#img{
  position: absolute;
  top: -100%;
  left: -100%;
  display: block;
  width: 100px;
  height: 100px;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  -webkit-clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  z-index: 99;
}
let imgDom = document.getElementById('img');
imgDom.style.width = Math.cos(Math.PI / 6) * radius * 2 + 'px';
imgDom.style.height = radius * 2 +'px';

鼠标交互

实现的鼠标交互的效果是:当鼠标移动时候,计算鼠标位置和圆心位置距离最近的点进行定位并绘制当前的六边形

// position image
imgDom.src = currentTarget.img.src;
imgDom.style.left = currentTarget.x - Math.cos(Math.PI / 6) * radius + "px";
imgDom.style.top = currentTarget.y - radius + "px";
// draw hexagon
context.beginPath();
for(let i = 0; i < 6; i += 1) {
  if(i === 0) {
    context.moveTo(currentTarget.x + Math.cos(Math.PI / 6)*radius, currentTarget.y + Math.sin(Math.PI / 6)*radius);
  } else {
    context.lineTo(currentTarget.x + Math.cos(Math.PI / 6 * (1 + 2 * i))*radius, currentTarget.y + Math.sin(Math.PI / 6 * (1 + 2 * i))*radius);
  }
}
context.closePath();

后文

当然,我们还考虑了六边形图片随机播放圆心的计算等问题。

感兴趣的读者可以进入 https://code.Admin.net/pen/7200732315646558267 体验 ❤️