首先需要取得图片或者文字的像素点,可以将它们绘制到 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()