likes
comments
collection

Canvas-六边形列阵背景-密恐慎入

作者站长头像
站长
· 阅读数 23

-- 在简单的特效中激发无尽的灵感。

前言

作为一个前端,闲着无聊的时候总喜欢玩一下 canvas,继上次中秋节活动记录了一个 星空月色 之后,又搞了一个六边形列阵背景,效果是这样的:

Canvas-六边形列阵背景-密恐慎入

这个效果其实并不难,我的目的是想实现任意组合的六边形组合效果或者以六边形为基准做出一些丰富的视觉效果,进而再让他动起来,光想想就挺有意思的!

需求拆解

  1. 通过 id 获取 canvas,并且监听可视窗口的变化,同步更新可视区域。
  2. 计算 六边形 各项参数,并 列阵 画出六边形矩阵。
  3. 为 六边形 填充 背景。
  4. 【思维发散】
    1. 每个六边形背景图片各不相同,类似群像效果。
    2. 自由组合六边形,且只为被组合的六边形中有背景特效
    3. 动画 - 初始化加载动画效果,中间展开/角落开始展现
    4. 交互 - 鼠标移入六边形,该六边形高亮,其周围的六边形次高亮。
    5. 动画+交互,升级为轮播效果
    6. 动画+交互,每个六边形有各自的运动轨迹
    7. . . .

开发思路

本文记录中并没有实现需求拆解中的思维发散部分,但是根据本文是完全在很短的时间实现思维发散的内容的。

技术栈: html + js

  1. 封装一个 Canvas 基础类,可以被实体类继承,并可以被复用,包含如下功能:
    • 初始化 canvas
    • 清空 canvas
    • 监听浏览器可视范围变化
  2. 对 六边形 分析,计算六边形的各项参数:
    • 六边形绘制很简单,因为 正六边形 留个定点在一个圆上,和圆心连线后,各自占 1/6 圆。
    • 列阵六边形的话,其实就是找到正六边形的中心点也就是外切圆的圆心就好了。
    • 找到圆心以后,基于圆心把六边形一个个画出来就好啦!
  3. 向六边形中填充图片,或者填充其他东西。

代码解释

虽然技术栈是 html+js ,但是还是应用了一些比较新(IE不支持)的东西,所以懂得大家都懂,一起抵制IE吧!!!

代码主要包含这几个部分:

  • index.html 作为 demo 页面,用来承载 js 插件。
  • canvas.class.js 封装的 canvas 类,声明一个基础类被其他的 实体类 继承,并提高了复用性。
  • sixAngle.class.js 封装的 sixAngle 类,声明了一个 六边形 实体,并提高了复用性。
  • index.js 用来组装页面逻辑。

es6 之后,我们可以使用 <script type="module"></script> 的形式在里面直接是用 import 引入其他 js 文件,这也是 要把 多个类进行单独封装的一个原因。

index.html

因为是一个demo,所以html主要是体现了js如何使用,代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>六边形</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
</body>
<script type="module">
  import BackSixAngle from './index.js'
  var c = new BackSixAngle("canvas", { isScreen: true, size: 100, color: 'skyblue', border: 2 })
</script>
</html>

BackSixAngle 是 index.js 中 暴露出来的一个 class,里面传入两个参数:

  • id:canvas 的id
  • config:是一个对象,后面的各项配置项后面会有说明,当前配置:
    • isScreen: 随着浏览器可视范围变化而变化
    • size:每个六边形的大小
    • color:六边形的边框颜色(以后可能需要改)
    • border:边框粗细

canvas.class.js

主要是封装的 canvas 级别常用到的几个 api:

export default class Canvas {
  constructor(id, config) {

    if (id !== 0 && !id) {
      throw new Error('id 不能为空!')
    }

    this.cv = document.getElementById(id)
    this.ctx = this.cv.getContext('2d')

    this.config = Object.assign({
      isScreen: true
    }, config)

    this.initSize()
  }

  initSize() {
    this.cv.style.display = "block"

    if (this.config.isScreen) {
      this.cv.width = window.innerWidth
      this.cv.height = window.innerHeight
      window.addEventListener("resize", this.resizeCanvas.bind(this), false)
    } else {
      this.cv.width = this.cv.parentNode.offsetWidth
      this.cv.height = this.cv.parentNode.offsetHeight
    }

  }

  clearCanvas() {
    this.ctx.clearRect(0, 0, this.cv.width, this.cv.height)
  }

  resizeCanvas() {
    this.cv.width = window.innerWidth
    this.cv.height = window.innerHeight
    this.draw()
  }

}

主要有三个方法:

  • initSize:初始化全屏 canvas
  • clearCanvas:清空 canvas
  • resizeCanvas:重置 canvas,这里面有个注意点,可以看到在里面调用了 this.draw() ,但是 Canvas 并没有声明此方法,是因为 Canvas 并不会单独调用,而是一个基础类

sixAngle.class.js

这就是把 六边形 单独给提出来了。

绘制六边形需要如下5个参数,并声明一个 draw 方法。

export default class SixAngle {
  constructor(ctx, x, y, size, color) {
    this.ctx = ctx
    this.x = x
    this.y = y
    this.size = size || 20
    this.color = color || 'blue'
    this.draw()
  }

  draw() {
    const self = this
    // this.ctx.fillStyle = `rgb(${parseInt(Math.random() * 255)},${parseInt(Math.random() * 255)},${parseInt(Math.random() * 255)})`
    
    // 准备图片
    var img = new Image();
    img.src = "./1.jpg";

    img.onload = function () {
      self.ctx.beginPath()
      self.ctx.fillStyle = self.color
      self.ctx.strokeStyle = self.color
      self.ctx.lineWidth = 2
      
      // 绘制六边形
      for (let i = 0; i <= 360; i += 60) {
        self.ctx.lineTo(self.x + Math.sin(i * 2 * Math.PI / 360) * self.size, self.y + Math.cos(i * 2 * Math.PI / 360) * self.size)
      }
      
      self.ctx.stroke()
      self.ctx.save()
      self.ctx.clip()
      // self.ctx.drawImage(img, self.x - Math.sin(60 * 2 * Math.PI / 360) * self.size, self.y - self.size, Math.sin(60 * 2 * Math.PI / 360) * self.size * 2, self.size * 2)
      
      // 绘制图片并裁剪
      self.ctx.drawImage(img, self.x - Math.sin(60 * 2 * Math.PI / 360) * self.size, self.y - self.size, self.size * 2, self.size * 2)
      self.ctx.restore()
      self.ctx.closePath()
    };

  }
}

看代码的话,六边形的绘制,只需要关注中间的for循环,其实就是从圆心,以size为半径,找到6个点,依次连接就是六边形啦!

index.js

关键的来了,在这里将把上面两个 class 组合起来:

'use strict'
import Canvas from '../libs/canvas.class.js'
import SixAngle from '../libs/sixAngle.class.js'

export default class BackSixAngle extends Canvas {
  constructor(id, config) {
    super(id, config)

    this.size = this.config.size || 20
    this.border = this.config.border || 2
    this.centerSize = Math.sqrt(3) * this.size
    this.allPoints = this.getPoints()
    this.points = []
    // this.points = [[this.cv.width / 2, this.cv.height / 2]]
    this.pointNum = 0
    this.init()

    // this.setI = setInterval(this.update.bind(this), 1000 / 60)
  }

  init() {
    this.draw()
  }

  draw() {
    // this.points = Array(this.pointNum).fill({}).map((item, idx) => this.allPoints[idx])
    this.points = this.allPoints
    this.points.map(item => new SixAngle(this.ctx, item[0], item[1], this.config.size))
  }

  getPoints() {
    let arr = [], baseX = 0
    let lineIdx = 0
    let xSpeed = this.size * Math.sqrt(3)
    let ySpeed = this.size * 1.5

    for (let j = 0; j < this.cv.height + ySpeed; j += ySpeed) {
      baseX = lineIdx % 2 == 0 ? 0 : -xSpeed / 2
      for (let i = 0; i < this.cv.width + xSpeed; i += xSpeed) {
        arr.push([i + baseX, j])
      }
      lineIdx++
    }
    return arr
  }

  includePoint(arr, [x, y]) {
    arr.sort((a, b) => a[0] - b[0])
    arr.filter(item => item[0] > - x)
    for (let item of arr) {
      if (item[0] == x && item[1] == y) {
        return true
      }
    }
    return false
  }

  update() {
    this.pointNum + 1 <= this.allPoints.length ? this.pointNum++ : clearInterval(this.setI)
    this.clearCanvas()
    this.draw()
  }

}

这里面核心点是 getPoints() 这个方法,通过对六边形模型分析可得知,六边形 基数行偶数行 会错半个出来,所以会把奇数行和偶数行区分开来。

为了使代码结构清晰,所以声明了 xSpeed 和 ySpeed,代表步进。

至此所有的代码就分析完了。

后记

这是一个静态的 canvas 绘制的六边形填充的背景图片,其实可以看到 里面是有 update() 方法的,看过我另一篇关于中秋的canvas效果,就会知道 update 方法的作用,它是为动画做准备的。

也就是说,在需求拆解中提到的一些思维发散,基于现在的版本是很容易实现的。 而且,感兴趣的话,可以为每一个六边形添加上交互,会做出更多的不一样的效果出来。

此外,六边形可以实现,那么随便几边形都是可以出现的,这样依赖,我们就可以做一个相册出来,用好多规则/不规则的图形作为相框。

只要给每个 point 加一个状态,就可以控制哪些点要绘制,哪些点不需要控制,这样就达到了组合六边形的效果。

更多的效果等待你来一起开发哟!