canvas实现粒子特效
前段时间在学习canvas,实现了一些有趣的功能,最近有时间就把它拿出来分享一下。
将这个功能先分析拆解一下:
我会按照以下顺序来进行实现:
1.图片作为背景
2.绘制一个静止小球
3.让一个小球运动起来
4.绘制多个小球并让他们运动起来
5.实现小球的反弹
这一步没有使用canvas的绘制图片功能,直接css样式走起。
background-image: url()
唯一要注意的点就是记得让背景图片的z-index: -2;
堆叠顺序最低即可。
这一步也没什么说的,就是在画布上随便画一个实心圆即可。
小球运动就是小球的圆心坐标xy改变
使用定时器实现移动,绘制前要清空前一帧
将绘制一个小球抽成一个方法
ballDraw(cX, cY, r);
// 绘制一个运动的圆
function ballDraw (x, y, r) {
setInterval(() => {
// 绘制前要清空前一帧的画布
cxt.clearRect(0, 0, canvas.width, canvas.height)
/* 绘制一个圆形 */
x += 2; // 圆心坐标每次+2
y += 2;
cxt.beginPath();
cxt.fillStyle = 'red';
cxt.arc(x, y, r, 0, 2*Math.PI);
cxt.fill();
}, 1000/60);
绘制小球cxt.arc(x, y, r, 0, 2*Math.PI);
将一个小球的数据都作为一个对象,多个对象合并成一个数组。
x轴和y轴的范围是-2到2。二者合并即为运动的方向和速度。
小球的圆心、半径、颜色、透明度、速度、方向都随机生成。
let setArr = [];
let maxNum = 30;
// 绘制生成多个圆
ballSet();
function ballSet () {
let timer1 = setInterval(() => {
// 控制小球总数
if (setArr.length >= maxNum) {
clearInterval(timer1);
}
let cX = 200 + Math.ceil(Math.random() * 200); // 200--400 Math.ceil对浮点数向上取整
let cY = 200 + Math.ceil(Math.random() * 200); // 初始Y坐标
let r = 5 + Math.ceil(Math.random() * 5); // 半径5-12
let red = Math.floor(Math.random() * 256); // 0--255
let green = Math.floor(Math.random() * 256); // rgba
let blue = Math.floor(Math.random() * 256);
let alpha = 0.5 + Math.random() * 0.3; // 0.5--0.8 透明度
let sX = -2 + Math.ceil(Math.random() * 4); // -2 --- 2 x轴速度
let sY = -2 + Math.ceil(Math.random() * 4);
let obj = {
cX: cX,
cY: cY,
r: r,
red: red,
green: green,
blue: blue,
alpha: alpha,
sX: sX,
sY: sY,
bgColor: `rgba(${red},${green},${blue},${alpha})`,
};
if (obj.sX !== 0 || obj.sY !== 0){ // 防止速度为0 ,不要静止的小球
setArr.push(obj);
}
}, 1000/20); // 每秒20
}
这里要注意因为使用Math.random()生成的速度,可能会出现x或y轴速度为0的小球,在画布上就会表现为水平移动、垂直移动或静止不动,我们要剔除这种小球。
// 绘制圆的运动
ballDraw(setArr);
function ballDraw (arr) {
setInterval(() => {
// 绘制前要清空前一帧的画布
cxt.clearRect(0, 0, canvas.width, canvas.height)
arr.forEach(item => { // 循环遍历setArr生成小球
/* 绘制一个圆形 */
item.cX += item.sX;
item.cY += item.sY;
cxt.beginPath();
cxt.fillStyle = item.bgColor;
cxt.arc(item.cX, item.cY, item.r, 0, 2*Math.PI);
cxt.fill();
})
}, 1000/60); // 每秒60
}
反弹的原理更简单,小球的边缘碰到画布边界后将x轴和y轴的速度坐标取反即实现反弹。
要注意:小球的圆心x坐标加上半径r大于等于右边界x坐标即代表小球碰撞到边界。同理上、下、左边界的碰撞也是类似的算法。
// 绘制圆的运动
ballDraw(setArr);
function ballDraw (arr) {
setInterval(() => {
// 绘制前要清空前一帧的画布
cxt.clearRect(0, 0, canvas.width, canvas.height)
arr.forEach(item => {
/* 边界碰撞处理 - 速度取反 */
if ((item.cX + item.r >= canvas.width) || (item.cX - item.r <= 0)) { // 碰到左右边界,反向弹
item.sX *= -1
}
if ((item.cY + item.r >= canvas.height) || (item.cY - item.r <= 0)) { // 碰到上下边界,反向弹
item.sY *= -1
}
item.cX += item.sX;
item.cY += item.sY;
/* 绘制一个圆形 */
cxt.beginPath();
cxt.fillStyle = item.bgColor;
cxt.arc(item.cX, item.cY, item.r, 0, 2*Math.PI);
cxt.fill();
})
}, 1000/60); // 每秒60
}
粒子特效
#canvas {
width: 100%;
background-image: url('./img/997.jpg');
background-size: 100%;
z-index: -2;
position: absolute;
left: 0;
top: 0;
}
h2 {
position: absolute;
top: 50px;
left: 50%;
margin-left: -140px;
font-size: 50px;
color: aliceblue;
text-align: center;
letter-spacing: 20px; /* 文字间隔 */
}
let canvas = document.querySelector('#canvas');
let cxt = canvas.getContext('2d');
let setArr = [];
let maxNum = 30;
// 绘制生成多个圆
ballSet();
function ballSet () {
let timer1 = setInterval(() => {
// 控制小球总数
if (setArr.length >= maxNum) {
clearInterval(timer1);
}
let cX = 200 + Math.ceil(Math.random() * 200); // 200--400 Math.ceil对浮点数向上取整
let cY = 200 + Math.ceil(Math.random() * 200); // 初始Y坐标
let r = 5 + Math.ceil(Math.random() * 5); // 半径5-12
let red = Math.floor(Math.random() * 256); // 0--255
let green = Math.floor(Math.random() * 256); // rgba
let blue = Math.floor(Math.random() * 256);
let alpha = 0.5 + Math.random() * 0.3; // 0.5--0.8 透明度
let sX = -1 + Math.ceil(Math.random() * 6); // -1 --- 5 x轴速度
let sY = -1 + Math.ceil(Math.random() * 6);
let obj = {
cX: cX,
cY: cY,
r: r,
red: red,
green: green,
blue: blue,
alpha: alpha,
sX: sX,
sY: sY,
bgColor: `rgba(${red},${green},${blue},${alpha})`,
};
if (obj.sX !== 0 || obj.sY !== 0){ // 防止速度为0 ,不要静止的小球
setArr.push(obj);
}
}, 1000/20); // 每秒20
}
// 绘制圆的运动
ballDraw(setArr);
function ballDraw (arr) {
setInterval(() => {
// 绘制前要清空前一帧的画布
cxt.clearRect(0, 0, canvas.width, canvas.height)
arr.forEach(item => {
/* 边界碰撞处理 - 速度取反 */
if ((item.cX + item.r >= canvas.width) || (item.cX - item.r <= 0)) { // 碰到左右边界,反向弹
item.sX *= -1
}
if ((item.cY + item.r >= canvas.height) || (item.cY - item.r <= 0)) { // 碰到上下边界,反向弹
item.sY *= -1
}
item.cX += item.sX;
item.cY += item.sY;
/* 绘制一个圆形 */
cxt.beginPath();
cxt.fillStyle = item.bgColor;
cxt.arc(item.cX, item.cY, item.r, 0, 2*Math.PI);
cxt.fill();
})
}, 1000/60); // 每秒60
}
咳咳!没什么可总结的,都是简单的代码-_-||