Canvas粒子进度条

2,252 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

在开发过程中,常常会遇到进度条的需求,本次就是给进度条添加一下好看的效果,比如说在进度条的运动过程中,有粒子不断的漂浮而出。

效果:

考虑到粒子会非常多,那么就不能使用简单的DOM元素去生成一粒粒的粒子

所以我使用了Canvas画布去进行粒子的绘制,至于进度条虽然可以绘制在canvas画布中,但是考虑到方便对已有的进度条添加粒子效果,本次的进度条先使用div元素绘制了。

进度条

那么先创建一个进度条吧

我使用是vue3,先定义html

<template>
    <div class="process">
	    <!-- 进度条 -->
        <div class="process-bar">
            <div class="current" id="create-particles"></div>
        </div>
    </div>
</template>
<script setup lang="ts">
    import { ref, reactive, onMounted } from "vue";
    let processdata = ref({
        d: 45 + '%'
    })
    onMounted(()=>{  })
</script>

css使用了预编译的less

<style scoped lang="less">
// 宽高
.wd(@w, @h) {
    width: @w;
    height: @h;
}
.process {
    .wd(100%, 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #1E1E1E;
    .process-bar {
        @width: 300px;
        @height: 20px;
        .wd(@width, @height);
        background-color: #000;
        border-radius: calc(@height / 2);
        .current {
            width: v-bind('processdata.d');
            height: @height;
            background-color: #A543DC;
            border-radius: calc(@height / 2);
        }
    }
}
</style>

这样一来,就能出现一个进度在45%的进度条了

p1.png

粒子方法

那么接下来就是重点的粒子方法,粒子是用canvas画布绘制的,所以需要使用document.createElement('canvas')去创建一个canvas元素

// 粒子画布初始化方法
const initparticles = (width: number, height: number, color: string, nums: number, startend: boolean)=>{
	let canvasParticles = <HTMLCanvasElement>document.getElementById('myCanvasParticles');
	let fatherDom = document.getElementById("create-particles");
	if (!fatherDom) return ;
	// 如果没有canvas
	if (!canvasParticles) {
		canvasParticles = document.createElement('canvas');          
		canvasParticles.id = "myCanvasParticles";
		fatherDom.appendChild(canvasParticles);
	}
	canvasParticles.width = width;
	canvasParticles.height = height;
	canvasParticles.style.top = `calc(50% - ${height / 2}px)`;

	......
}

初始化粒子画布方法传入4个参数:

参数作用
width画布的宽度
height画布的高度
color粒子的颜色
nums粒子的数量
startend粒子是否开始渲染/结束渲染

这样就可以在#create-particles元素中创建画布了,然后在less对画布元素修改为绝对定位:

#create-particles {
	position: relative;
	#myCanvasParticles {
		position: absolute ;
		right: 0px ;
	}
}

这样画布的位置就会在进度条靠右侧的位置,并且上下居中

p2.png

在画布中,有许许多多的粒子,并且每个粒子的运动方向和颜色都是不同的,所以可以创建一个粒子的类

// 画布
let ctx = canvasParticles.getContext("2d") as CanvasRenderingContext2D;
// 定义粒子类
class Partical {
	// 粒子坐标
	x: number = 0;
	y: number = 0;
	initx: number;
	inity: number;
	// 粒子速度
	speed: number = 0;
	// 粒子运动方向
	directionAngle: number = 0;
	// 粒子大小
	radius: number = 2;
	// 粒子在两方向上运动速度
	vector: { x: number, y: number };
	// 画布大小
	canvas: { w: number, h: number };
	constructor(...data: [x: number, y: number, speed: number, directionAngle: number, canvas: { w: number, h: number }]) {
		[this.x, this.y, this.speed, this.directionAngle, this.canvas] = data;
		[ this.initx, this.inity] = [this.x, this.y];
		this.vector = {
			x: -Math.abs(this.speed * Math.cos(this.directionAngle * Math.PI / 180)),       //粒子在x轴的速度
			y: this.speed * Math.sin(this.directionAngle * Math.PI / 180),        //粒子在y轴的速度
		}
	};
	//绘制粒子的函数
	draw(color: string){
            ...
	}
}

这样先new一个粒子的实例出来看看

let one = new Partical(20, 40, 1, 120 + Math.floor(Math.random() * (140 - 120)), { w: width, h: height });
one.draw('#ff0');

p3.png

目前粒子是不会运动的,所以需要在class类中添加运动方法

// 粒子更新方法
update(color: string) {
	this.x += this.vector.x;
	this.y += this.vector.y;
	this.draw(color);
}

重新调整一下粒子的初始位置到进度条的右侧,并且使用requestAnimationFrame设置定时运动方法,就可以看到以下效果:生成的小黄点粒子已经开始在运动了

g1.gif

既然一个粒子已经生成好啦,那么接下来就是生成nums的数量的粒子

g2.gif

但是此时当粒子从画布中超出后就一次性结束了,所以需要重新让粒子回到原起始位置,然粒子不断循环起来

// 飞出画布外
[this.x, this.y] = [ this.initx, this.inity];

g3.gif

但是此时又出现了一个新问题,那就是粒子最初是同步出现的,这样虽然后面会因为每个粒子的方向不同,显现出不同。但是最初的时候还是一下子就会全部发散出去。

所以可以在requestAnimationFrame中再生成粒子实例,每十次requestAnimationFrame循环生成一个粒子