uniapp中使用canvas和requestAnimationFrame显示粒子动画

实现效果

  1. 在canvas中显示根据帧率(fps)和粒子数量(numbers)生成随机颜色大小以及速度的粒子(particles)。
  2. 帧率和粒子数量可随时更改,更改完后视图也会更新。
  3. 添加开始动画和停止动画按钮。

实现思路

粒子

使用Math.random()获取粒子颜色、大小以及xy轴的速度并存到数组里

// 定义粒子类
function Particle(x, y, speedX, speedY, radius, color) {
  this.x = x;
  this.y = y;
  this.speedX = speedX;
  this.speedY = speedY;
  this.radius = radius;
  this.color = color;
}
// 创建粒子
createParticles() {
  for (let i = 0; i < this.numbers; i++) {
    const x = Math.random() * canvas.width;
    const y = Math.random() * canvas.height;
    const speedX = Math.random() * 4 - 2;
    const speedY = Math.random() * 4 - 2;
    const radius = Math.random() * 5 + 1;
    const color = this.getRandomColor();

    this.particles.push(new Particle(x, y, speedX, speedY, radius, color));
  }
}
// 随机生成颜色
getRandomColor() {
  const letters = '0123456789ABCDEF';
  let color = '#';

  for (let i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }

  return color;
}

使用ctx.arc方法绘制圆形粒子

// 绘制粒子
Particle.prototype.draw = function() {
  ctx.beginPath();
  ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
  ctx.fillStyle = this.color;
  ctx.fill();
  ctx.closePath();
}

动画

给粒子添加更新位置方法,并在requestAnimationFrame中调用。
根据fps获取没此更新位置的时间,再与当前时间至上一次更新位置的时间差做比较就一块实现随刷新率更新粒子。

// 更新粒子的位置
Particle.prototype.update = function() {
  this.x += this.speedX;
  this.y += this.speedY;

  // 如果粒子超出画布边界,则改变速度方向
  if (this.x + this.radius < 0 || this.x - this.radius > canvas.width) {
    this.speedX = -this.speedX;
  }

  if (this.y + this.radius < 0 || this.y - this.radius > canvas.height) {
    this.speedY = -this.speedY;
  }
}
// 更新粒子位置
updateParticles() {
  this.particles.forEach((particle) => {
    particle.update();
  });
}
// 开始动画
startAnimation() {
	this.now = this.now || Date.now();
	if ((Date.now() - this.now) > (1000 / (this.fps || 1))) {
		this.now = Date.now();
		this.updateParticles();
	}
	this.raf = requestAnimationFrame(this.startAnimation);
}

问题点

  • 点击多次开始动画按钮,需要判断动画是否已经开始
    requestAnimationFrame方法会返回一个id,判断id不存在再调用startAnimation方法就行
  • 改变粒子数量应该初始化再生成粒子数组
    使用watch监听numbers判断是否有动画,有就停止动画。再依次调用重置参数方法,初始化方法,开始动画方法。
  • uniapp的canvas获取不到ctx
    有两种方法:
    第一种,canvas添加属性canvas-id,然后调用uni.createCanvasContext方法就可以创建canvas绘图上下文。
    第二种,直接获取canvas元素的childNodes拿到原生canvas元素再使用getContext。

整体代码

代码重要参数如下

  • canvas - 存储uniapp的canvas元素用以获取宽高等参数
  • ctx - 原生canvas的绘图上下文对象
  • raf - 帧函数的id,用以关闭动画等
  • now - 记录渲染时间
  • fps - 控制每秒渲染次数
  • numbers - 控制粒子数量
  • particles - 存储粒子信息
let canvas = null;
let ctx = null;

// 定义粒子类
function Particle(x, y, speedX, speedY, radius, color) {
  this.x = x;
  this.y = y;
  this.speedX = speedX;
  this.speedY = speedY;
  this.radius = radius;
  this.color = color;
}

// 更新粒子的位置
Particle.prototype.update = function() {
  this.x += this.speedX;
  this.y += this.speedY;

  // 如果粒子超出画布边界,则改变速度方向
  if (this.x + this.radius < 0 || this.x - this.radius > canvas.width) {
    this.speedX = -this.speedX;
  }

  if (this.y + this.radius < 0 || this.y - this.radius > canvas.height) {
    this.speedY = -this.speedY;
  }
};

// 绘制粒子
Particle.prototype.draw = function() {
  ctx.beginPath();
  ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
  ctx.fillStyle = this.color;
  ctx.fill();
  ctx.closePath();
};

export default {
	data() {
		return {
			raf: 0, // 帧函数
			now: 0, // 当前时间
			fps: 60, // 帧率
			numbers: 100, // 粒子数量
			particles: [], // 粒子数组
		}
	},
	watch: {
		numbers: {
			handler(newVal) {
				this.raf && this.stopAnimation()
				this.reset()
				this.init()
				this.startAnimation()
			}
		}
	},
	mounted() {
		this.$nextTick(() => {
			this.init()
		})
	},
	methods: {
		// 初始化
		init() {
			canvas = document.querySelector('#canvasParticles').getBoundingClientRect() //获取 canvas 组件
			ctx = document.querySelector('#canvasParticles').childNodes[0].getContext('2d') //获取 canvas 绘图上下文
			this.createParticles();
			this.drawParticles();
		},
		reset() {
			this.raf = 0
			this.now = 0
			this.particles = []
		},
		// 创建粒子
		createParticles() {
		  for (let i = 0; i < this.numbers; i++) {
		    const x = Math.random() * canvas.width;
		    const y = Math.random() * canvas.height;
		    const speedX = Math.random() * 4 - 2;
		    const speedY = Math.random() * 4 - 2;
		    const radius = Math.random() * 5 + 1;
		    const color = this.getRandomColor();
		
		    this.particles.push(new Particle(x, y, speedX, speedY, radius, color));
		  }
		},
		// 更新粒子位置
		updateParticles() {
		  this.particles.forEach((particle) => {
		    particle.update();
		  });
		},
		// 开始动画
		startAnimation() {
			this.now = this.now || Date.now();
			if ((Date.now() - this.now) > (1000 / (this.fps || 1))) {
				this.now = Date.now();
				this.updateParticles();
			}
			this.raf = requestAnimationFrame(this.startAnimation);
		},
		// 绘制粒子
		drawParticles() {
			ctx.clearRect(0, 0, canvas.width, canvas.height);

			this.particles.forEach((particle) => {
				particle.draw();
			});

			requestAnimationFrame(this.drawParticles);
		},
		// 停止动画
		stopAnimation() {
			cancelAnimationFrame(this.raf);
			this.raf = 0
		},
		// 随机生成颜色
		getRandomColor() {
		  const letters = '0123456789ABCDEF';
		  let color = '#';
		
		  for (let i = 0; i < 6; i++) {
		    color += letters[Math.floor(Math.random() * 16)];
		  }
		
		  return color;
		},
	}
}

本来还想把fps最大值设置成屏幕刷新率,但是我在uniapp官网没找到获取屏幕刷新率的方法,如果有大佬找到获取屏幕刷新率的方法还请告知。

posted @ 2026-02-09 10:23  Ccarrote  阅读(68)  评论(0)    收藏  举报