实现3D图文粒子化

图片/文字粒子化

首先需要取得图片或者文字的像素点,可以将它们绘制到 canvas 上得到它们的像素点,主要使用 getImageData(x, y, width, height) 方法获取。然后通过遍历画布的大小得到每个位置像素点,为了让粒子之间互相有间隔,遍历的时候用一个变量来控制像素点数量,最后再将透明的点(alpha值小于128)过滤掉即可得到最终的像素点。

/** * 获取像素点 * @param {Object|String} target Image对象或者一段文字 * @param {Number} gird 决定像素点数量的值 */
function getImageData (target, gird = 12) {
  // 创建一个canvas用于绘制目标
  const canvas = document.createElement('canvas')
  const width = canvas.width = window.innerWidth
  const height = canvas.height = window.innerHeight
  const ctx = canvas.getContext('2d')

  if (typeof target === 'string') {
    // 绘制文字
    ctx.font = '420px bold'
    ctx.fillStyle = '#fff'
    ctx.textBaseline = 'middle'
    ctx.textAlign = 'center'
    ctx.fillText(target, width / 2, height / 2)
  } else {
    // 绘制图片
    ctx.drawImage(target, (width - target.width) / 2, (height - target.height) / 2 , target.width, target.height)
  }

  // 获取所有的像素,返回结果为一个数组: [r, g, b, a, r, g, b, a, ...]
  const data = ctx.getImageData(0, 0, width, height).data
  const items = []
  // 遍历画布所有像素,利用gird控制遍历像素的数量,防止获取过多的像素点
  for (let x = 0; x < width; x += gird) {
    for (let y = 0; y < height; y += gird) {
      // 获取当前像素点位于data数组里的位置,一个像素点由r、g、b、a组成。
      const pos = (y * width + x) * 4
      // alpha值大于128则为有效像素点
      if (data[pos + 3] > 128) {
        // 存储像素点坐标及颜色
        items.push({ x, y, rgba: [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]] })
      }
    }
  }
  return items
}

离散动画实现

将粒子目标位置xy与当前位置xy相减得到最终要移动的距离,根据公式可求出两点间的距离:

当距离小于1(像素)的时候,则当前这个粒子动画完成,运用字典存储每个小球的状态,如果当前这个粒子完成了则状态设为 true

function animate () {
  // 缓动速度,值越大动画速度越快
  const easing = 0.1
  // 遍历所有点
  balls.forEach((ball, index) => {
    // 通过点的目标值减去当前状态的值获取差值dx、dy、dz、dr
    const dx = ball.tx - ball.xpos // x坐标
    const dy = ball.ty - ball.ypos // y坐标
    const dz = ball.tz - ball.zpos // z坐标
    const dr = ball.tr - ball.radius // 半径
    // 求2点之间的距离,这个里可以把 dz 和 dr 也加进去计算
    const dist = Math.sqrt(dx * dx + dy * dy + dz * dz + dr * dr)
    // 将点当前的位置与差值乘以缓动速度的值相加
    ball.xpos += dx * easing
    ball.ypos += dy * easing
    ball.zpos += dz * easing
    ball.radius += dr * easing
    // 如果2点间的距离小于1则当前这个像素点动画完成
    if (dist < 1) {
      if (!complete[ball._id]) {
        complete.length++
        complete[ball._id] = true
      }
      // 如果所有的像素点都完成了则设置 loadAnimation 状态为 false
      if (complete.length === ballLength) {
        loadAnimation = false
        complete = { length: 0 }
      }
    }
    // 透视图计算
    setPerspective(ball)
  })
}

// 绘制画布
function drawFrame () {
  window.requestAnimationFrame(drawFrame)
  ctx.clearRect(0, 0, width, height)
  angleY = (mouse.x - vpX) * 0.00005
  angleX = (mouse.y - vpY) * 0.00005
  balls.sort(zsort)
  // 如果loadAnimation状态为真则执行动画
  if (loadAnimation) {
    animate()
  } else {
    balls.forEach(move)
  }
  balls.forEach(draw)
}

drawFrame()

像素点的三维效果实现

创建三维图形三维坐标旋转三维Z轴、透视图