canvas 两种拖尾效果实现烟花

前言

年关将至,***市区又禁放烟花,但是又非常想看烟花肿么办呢,我们可以用 js 在网页上实现烟花的效果,下面我们将功能进行拆解。

步骤分解

将烟花效果拆分 2 个阶段:

  1. 烟花上升过程
  2. 上升到某个点后,爆炸生成碎片逐渐消失效果

实现思路确定好开始用代码去实现效果

烟花上升效果

粒子上升

通过定时器不断的去更新粒子y轴值,同時增加vy达到一个加速的感觉,age是当前粒子存活时间。

ini 复制代码
// 烟花粒子
class Firework {
  constructor({ x, y, vy, age }) {
    this.x = x;
    this.y = y;
    this.vy = vy || random(0, 1);
    this.age = age || random(200, 400);
  }
  update() {
    this.y -= this.vy;
    this.vy += 0.01;
    this.age--;
    this.draw();
  }
  draw() {
    ctx.fillStyle = "#fff";
    ctx.beginPath();
    ctx.arc(this.x, this.y, 2, 0, Math.PI * 2, 0);
    ctx.closePath();
    ctx.fill();
  }
}
let firework = new Firework({
  x: canvas.width * Math.random(),
  y: canvas.height,
});
setInterval(() => {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除矩形
  if (firework) {
    firework.update();
    firework.draw();
    if (firework.age < 0) {
      firework = null;
    }
  }
}, 60 / 1000);

从上面效果图可以看到粒子上升的过程非常单调,如果增加流光尾迹效果更加真实、炫酷。

两种方式实现粒子拖尾效果

  1. 幕布覆盖
  2. 维护运动轨迹记录帧+渐变实现
幕布覆盖

在定时器里面把清除矩形方法(clearRect)去掉,通过带有透明度的幕布进行覆盖即可实现拖尾效果。加上这两行代码。

ctx.fillStyle = "rgba(0,0,0,0.1)"; // 填充颜色

ctx.fillRect(0, 0, canvas.width, canvas.height); // 填充矩形

ini 复制代码
setInterval(() => {
  // ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除矩形
  ctx.fillStyle = "rgba(0,0,0,0.1)"; // 填充颜色,透明度越小尾迹越长
  ctx.fillRect(0, 0, canvas.width, canvas.height); // 填充矩形
  if (firework) {
    firework.update();
    firework.draw();
    if (firework.age < 0) {
      firework = null;
    }
  }
}, 60 / 1000);
维护运动轨迹记录帧+渐变实现

定时器里面添加清除矩形方法,清除上一帧,在Firework内部维护trailingArray数组用来记录之前每一帧,超过三十个时剔除掉之前帧。在每次绘制时将trailingArray首尾帧连接,并设置渐变即可实现拖尾效果。

kotlin 复制代码
class Firework {
  constructor({ x, y, vy, age, ctx, isTailingEnabled }) {
    // ... 之前代码
    this.trailingArray = [];
  }
  update() {
    // ... 之前代
    this.trailingArray.unshift({ x: this.x, y: this.y }); // 在拖尾的开始添加新点
    if (this.trailingArray.length > 30) {
      this.trailingArray.pop();
    }
    this.draw();
  }
  draw() {
    // ... 之前代
    // 绘制拖尾
    this.updateTrailing();
  }
  updateTrailing() {
    ctx.beginPath();
    var lastItem = this.trailingArray[this.trailingArray.length - 1];
    var gradient = ctx.createLinearGradient(
      this.x,
      this.y,
      lastItem.x,
      lastItem.y
    ); // 创建渐变对象
    gradient.addColorStop(0, "#fff"); // 在起点处设置颜色为同色
    gradient.addColorStop(1, "transparent"); // 在终点处设置颜色为透明
    ctx.lineWidth = 2;
    ctx.moveTo(this.x, this.y); // 从当前粒子位置开始绘制
    ctx.lineTo(lastItem.x, lastItem.y); // 连接每个拖尾点
    ctx.strokeStyle = gradient; // 设置线条颜色为粒子的颜色
    ctx.stroke(); // 绘制线条到最后一个拖尾点
  }
}
// firework粒子实例
setInterval(() => {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除矩形
  if (firework) {
    firework.update();
    firework.draw();
    if (firework.age < 0) {
      firework = null;
    }
  }
}, 60 / 1000);

以上就是用不同的方式去实现拖尾效果,接下来我们要实现的就是达到某个高度时爆炸的效果。

烟花爆炸

以圆为切入点,循环把每个点绘制成圆,然后不断增加半径长度即达到烟花散开的效果。

ini 复制代码
let count = 100,rx = 600, ry = 300,r = 0;
function draw() {
  for (let i = 0; i < count; i++) {
    let angle = (360 / count) * i; // 求出各个点的角度
    let radians = (angle * Math.PI) / 180; // 转换为弧度制
    let moveX = rx + Math.cos(radians) * r;
    let moveY = ry + Math.sin(radians) * r;
​
    ctx.fillStyle = "#fff";
    ctx.beginPath();
    ctx.arc(moveX, moveY, 2, Math.PI * 2, false);
    ctx.closePath();
    ctx.fill();
  }
}
​
let timer = setInterval(() => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  r += 0.4;
  draw();
}, 60 / 1000);

上图已经实现烟花碎片运动的效果,但是效果看起来很不真实,碎片移动太过整齐、无重力效果。我们可将烟花碎片粒子封装成类,在初始化的时候用不同的增长速度让烟花碎片进行无规则的运动,并在y轴稍微增加一些值让碎片看起来有下坠感,更接近真实效果。

kotlin 复制代码
 class Ball {
  constructor({ fillStyle, rx, ry, ctx, v, r, radians }) {
    this.ctx = ctx;
    this.fillStyle = fillStyle || getRandomColor();
    this.rx = rx; // 圆心x位置
    this.ry = ry; // 圆心y位置
    this.r = r || 0; // 初始半径
    this.radians = radians || 0; // 弧度制
    this.v = v || 0.2; // 速率
    this.yh = 0; // 增长值
    this.vy = 0.04; // 增长值速度
  }
  update() {
    // 1. 每个碎片实例 new 的时候传入不同的 this.v 增长速度;
    this.r += this.v;
    // 2. 需要碎片有下坠感,需要增加yh单独y的增长高度,加在原来的moveY上就达到下坠的感觉
    // 2. 需要加速的感觉需要单独维护 vy 增长速度
    this.yh += this.vy;
    this.vy += 0.01;
    let moveX = this.rx + Math.cos(this.radians) * this.r;
    let moveY = this.ry + this.yh + Math.sin(this.radians) * this.r;
    this.draw(moveX, moveY);
  }
  draw(moveX, moveY) {
    this.ctx.fillStyle = this.fillStyle;
    this.ctx.beginPath();
    this.ctx.arc(moveX, moveY, 2, Math.PI * 2, false);
    this.ctx.closePath();
    this.ctx.fill();
  }
}

以上就是两大核心的实现思路,只要我们将这些功能组合起来,就形成一个完整的烟花啦。

结尾

到这里,本文的所有内容就结束了。主要分析两个难点实现思路,感兴趣的同学可以领取源码在原来基础上拓展。

  • 粒子上升拖尾效果
  • 烟花碎片爆炸效果

最后希望看到这里的同学都有所收获,也希望同学能给一个小小的赞🌹🌹🌹,需要源码,关注公众号三哈同学,回复「烟花效果」领取。

相关推荐
我爱李星璇6 分钟前
HTML常用表格与标签
前端·html
疯狂的沙粒9 分钟前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员25 分钟前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐27 分钟前
前端图像处理(一)
前端
程序猿阿伟34 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒36 分钟前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪1 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M1 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
weiabc1 小时前
学习electron
javascript·学习·electron