前言
前两天我女同事看到了明日方舟首页的一个粒子动画效果,感觉非常炫酷,就问我这个效果是怎么实现的。身为一个自尊心颇强男性,不允许我说不行,于是就做了一个简单的demo,效果图如下(实现灰常简单,几十行代码搞定):
- Demo1
- Demo2
前期准备
- 一个html文件
- 一张剪影图片,如:
思路
- 准备一张剪影图片,通过canvas的drawImage绘制出来
- 通过getImageData获取图片像素点信息
- 匹配剪影的颜色值,如果像素点为黑色则生成白色圆点来替换
- 控制白色圆点的随机位置、速度等因素,使圆点动起来
开整
- 初始化canvas和img
<div class="container">
<canvas id="canvas" width="800" height="800"></canvas>
</div>
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext("2d")
const img = new Image();
img.src = "./imgs/剪鹿.png";
// 储存图片像素信息
let imgData = null
// 防止图片过大超过canvas大小,所以限制图片宽度为600,等比例缩放
let imgW = 600
let imgH = null
// 控制粒子扩散或聚合
let flag = true
img.onload = function () {
imgH = imgW * (img.height / img.width)
ctx.drawImage(img, 0, 0, imgW, imgH);
imgData = ctx.getImageData(0, 0, imgW, imgH).data;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 生成点的实例
pointInit(imgData)
move()
}
- 初始化白色圆点
function pointInit(imgData) {
const gap = 4;
for (var h = 0; h < imgH; h += gap) {
for (var w = 0; w < imgW; w += gap) {
var position = (imgW * h + w) * 4;
var r = imgData[position], g = imgData[position + 1], b = imgData[position + 2];
// 当rgb都为0时,说明颜色值为黑色
if (r + g + b === 0) {
pointArr.push(new Point(1, w, h))
}
}
}
}
- 创建圆点的类
class Point {
constructor(size, w, h) {
// 保留图像初始位置,方便粒子聚合成图像
this.orw = w
this.orh = h
// 随机位置
this.x = Math.random() * canvas.width
this.y = Math.random() * canvas.height
this.size = size
this.w = w
this.h = h
// 速度
this.spx = (w - this.x) / 2 / 20
this.spy = (h - this.y) / 2 / 20
}
// 圆点每次位置变化
update() {
this.spx = (this.w - this.x) / 2 / 20
this.spy = (this.h - this.y) / 2 / 20
if (Math.abs(this.w - this.x) <= Math.abs(this.spx)) {
this.x = this.w
} else {
this.x += this.spx
}
if (Math.abs(this.h - this.y) <= Math.abs(this.spy)) {
this.y = this.h
} else {
this.y += this.spy
}
}
// 渲染圆点
rander() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
ctx.fillStyle = '#fff';
ctx.fill()
ctx.closePath();
}
}
- 接下来让圆点动起来
function move() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
pointArr.forEach(point => {
point.update()
point.rander()
})
// 代替setTimeout提升性能
requestAnimationFrame(move)
}
- 点击事件控制粒子聚合
// 散开聚合
function changePic() {
flag = !flag
pointArr.forEach(point => {
point.w = flag ? point.orw : Math.random() * canvas.width
point.h = flag ? point.orh : Math.random() * canvas.height
})
}
到了这里,基本就已经做完了,粒子效果已经可以正常显示了,如果要换成其他的图形直接改变图片的src即可更换图形。
有什么问题可以在评论区讨论。(撰文不易,点赞鼓励)