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();
  }
}

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

结尾

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

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

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

相关推荐
卸任31 分钟前
Vibe coding工具使用心得
前端·ai编程
brien91 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance2 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M2 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写2 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李2 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易2 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding2 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马2 小时前
前沿视界
前端
kyriewen2 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员