内容简介:之前的文章提到过 canvas 动画的难点和一般规则。canvas 采用了一种叫做本文我们做一个粒子动画效果看看:从图中可以看出,粒子运动效果依据以下规则:
之前的文章提到过 canvas 动画的难点和一般规则。canvas 采用了一种叫做 立即渲染模式 ,调用绘制方法后 canvas 会直接将图形绘制到画布上,然后就不再管它,canvas 也不会记住你绘制的任何图形。这和 dom 模型不同,我们始终可以在内存中获取到 dom 元素的引用。基于这种特点,我们在动画每一帧之前都会清除画布,重新绘制一遍。绘制步骤如下:
- 初始化图形的状态,如坐标、大小、速度,运动方向等
- 清除画布,根据图形状态调用绘制函数画图
- 更新图形状态,返回步骤 2
本文我们做一个粒子动画效果看看:
需求分析
从图中可以看出,粒子运动效果依据以下规则:
- 画布上有固定若干个粒子和线组成,一开始生成n个随机坐标的粒子
- 粒子朝随机方向固定速度运动,当触碰到画布边缘时回弹
- 鼠标移动到画布上时,会在鼠标位置生成一个粒子
- 当两个粒子直线距离小于一定距离时连线,否则不连线或取消连线
实现分析(不带代码)
- 为了记录粒子的坐标、速度、方向、连线等状态,并封装绘图方法,我们需要一个粒子类,为了绘制n个粒子,我们需要一个数组在存放粒子对象。首先将粒子类初始化,赋予随机的坐标并给定初始速度和初始方向。
- 往粒子数组内追加一个鼠标粒子对象,坐标为当前鼠标位置,监听鼠标滑动并更新鼠标粒子坐标。
- 每一帧我们会按速度和方向更新粒子对象的坐标并重绘画布,当监测粒子坐标超出画布时,给定粒子类一个相反的方向。
- 当检测到两个粒子距离小于一定距离时,连线。
代码解读
首先是粒子类,它接受一个渲染上下文作为参数,并封装了粒子的状态和绘图方法。
class Particle { ctx: CanvasRenderingContext2D; x: number; y: number; vx: number; vy: number; constructor(ctx: CanvasRenderingContext2D) { this.ctx = ctx; this.x = random(0, CANVAS_WIDTH); this.y = random(0, CANVAS_HEIGHT); this.vx = random(-VELOCITY, VELOCITY); this.vy = random(-VELOCITY, VELOCITY); } draw() { if (this.x > CANVAS_WIDTH || this.x < 0) { this.vx = -this.vx; } if (this.y > CANVAS_HEIGHT || this.y < 0) { this.vy = -this.vy; } this.x += this.vx; this.y += this.vy; this.ctx.beginPath(); this.ctx.arc(this.x, this.y, PARTICLE_RADIUS, 0, Math.PI * 2); // this.ctx.closePath(); this.ctx.fill(); } } 复制代码
其次是鼠标粒子,它接受渲染上下文和dom容器作为参数,和粒子类相同,并且监听容器的鼠标位置以更新其坐标。
class MouseParticle { ctx: CanvasRenderingContext2D; x = -1; y = -1; // 单例的 static instance: MouseParticle; constructor(ctx: CanvasRenderingContext2D, container: HTMLCanvasElement) { this.ctx = ctx; if (MouseParticle.instance) return MouseParticle.instance; container.addEventListener("mouseenter", e => { this.x = e.clientX; this.y = e.clientY; }); container.addEventListener("mousemove", e => { this.x = e.clientX; this.y = e.clientY; }); container.addEventListener("mouseleave", e => { // 移到canvas外 this.x = -1; this.y = -1; }); MouseParticle.instance = this; return this; } draw() { this.ctx.beginPath(); this.ctx.arc(this.x, this.y, PARTICLE_RADIUS, 0, Math.PI * 2); this.ctx.fill(); } } 复制代码
接着我们初始化粒子和鼠标粒子对象,并保存进粒子数组。
const particles: (Particle | MouseParticle)[] = []; for (let index = 0; index < PARTICLE_COUNT; index++) { const particle = new Particle(ctx); particles.push(particle); } const mouseParticle = new MouseParticle(ctx, canvas); 复制代码
接下来是我们的主绘图函数,它会在每一帧调用,根据粒子的状态重绘画布。
function draw(ctx: CanvasRenderingContext2D) { requestAnimationFrame(() => draw(ctx)); // 清除画布并重绘粒子 ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); particles.forEach(particle => { particle.draw(); }); // 重绘线 ctx.save(); particles.forEach(source => { particles.forEach(target => { const xDistance = Math.abs(source.x - target.x); const yDistance = Math.abs(source.y - target.y); const dis = Math.round( Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2)) ); if (dis < PARTICLE_DISTANCE) { ctx.globalAlpha = dis / PARTICLE_DISTANCE; ctx.beginPath(); ctx.moveTo(source.x, source.y); ctx.lineTo(target.x, target.y); ctx.closePath(); ctx.stroke(); } }); }); } 复制代码
在主绘图函数里我们遍历粒子数组,当两个粒子距离小于一定距离时连线。
这样我们就完成一个粒子效果啦~~~
以上所述就是小编给大家介绍的《数据可视化之路 - canvas 粒子背景效果》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 深度高能粒子对撞追踪:Kaggle TrackML粒子追踪挑战赛亚军访谈
- 粒子滤波Matlab示例
- 粒子滤波Matlab示例
- 粒子系统的设计
- CAEmitterLayer 粒子动画
- 学习 PixiJS — 粒子效果
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。