「万事胜意🧧」你可能不知道的烟花秀🎆

4,443 阅读6分钟

PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

🙇 前言

  • 转眼间又到了一年春节,而今年的春节也正好是我的本命年🐯。
  • 因为疫情的原因可能又回不了家过年了,还是很想回去看看烟花感受下年味的🧧。
  • 既然回不去那就自己做一个吧~跟大家一起赏烟花🎆。
  • Ps: 本文没有涉及到性能优化,请不要在正式项目使用喔~

🏮 ToDoList

  • 获取文字像素点
  • 初始化烟花
  • 烟花发射
  • 烟花爆炸

🧧 Just Do It

获取文字像素点

  • 首先我们先创建一个canvas
<!-- index.html -->
<canvas id="textFireWorks"></canvas>
  • 初始化canvas并在canvas中填充想要输出的文字
// fireWork.js
let textCanvas, textCtx;
const canvasWidth = window.innerWidth;
const canvasHeight = window.innerHeight;
const fontSize =180
function fireWorksShowBegin() {
  initCanvas();
}
function initCanvas() {
  textCanvas = document.getElementById("textFireWorks");
  textCtx = textCanvas.getContext("2d");
  textCanvas.style.width = canvasWidth + "px";
  textCanvas.style.height = canvasHeight + "px";
  textCanvas.width = canvasWidth;
  textCanvas.height = canvasHeight;
  textCtx.textAlign = "center";
  textCtx.textBaseline = "top";
  textCtx.font = fontSize + 'px "宋体"';
  textCtx.fillStyle = "#fff";
  textCtx.fillText("新春快乐", canvasWidth / 2, 0);
}

  • canvas初始化完成后我们需要获取每个像素点对应的位置
// fireWork.js
let textPixels = [];
function initCanvas() {
  // ...
  //获取画布位置
  let pix = textCtx.getImageData(0, 0, canvasWidth, canvasHeight).data;
  let gap = 6;
  for (let h = 0; h < canvasHeight; h += gap) {
    for (let w = 0; w < canvasWidth; w += gap) {
      // 当前像素块相对于画布的索引位置
      let position = (canvasWidth * h + w) * 4;
      let r = pix[position],
          g = pix[position + 1],
          b = pix[position + 2];
      if (r + g + b !== 0) {
        textPixels.push({
          x: w,
          y: h,
        });
      }
    }
  }
 }
  • canvas中我们可以通过getImageData方法拿到画布的信息
  • 其中的data就是色彩信息,每4个元素表示一个像素颜色
  • 我们可以通过以上算法在水平和垂直方向均以固定间隙去读取imageData像素点信息,如果是完全不透明的像素点,则作为我们需要的关键坐标保存下来至textPixels数组中用于我们之后烟花渲染。

初始化烟花

  • 对于烟花的呈现在这里我使用了一个PixiJS库,这是一个HTML5创建引擎,用它可以很方便的渲染2D动画。
  • 首先引入该库后我们创建一个容器和渲染器添加到我们的dom中。
// fireWork.js
// 创建一个Container
const stage = new PIXI.Container();
//自动检测渲染器
const renderer = PIXI.autoDetectRenderer(canvasWidth, canvasHeight);
document.body.appendChild(renderer.view);
  • 接下来我们需要将烟花最终绽放的位置摆满烟花,也就是以上像素点的位置。
  • 使用PIXI.Texture我将福的图标作为烟花占位符。
  • 将每一个像素点都替换成福图标。
  • stage容器中添加这些子节点,并把每一个firework记录下来方便接下来使用。
// fireWork.js
const fireworks = [];
const yOffset = canvasHeight * 0.4;
const textures = PIXI.Texture.from("https://s3.bmp.ovh/imgs/2022/01/0d7afb4d0700761e.png");
function fireWorksShowBegin() {
  //...
  initFireworks();
}
function initFireworks() {
  // shuffle(textPixels);
  for (let i = 0, l = textPixels.length; i < l; i++) {
    createEmojiFirework(textures, textPixels[i], i);
  }
}
function createEmojiFirework(text, pos, i) {
  const size = 20;
  const firework = new PIXI.Sprite(text);
  firework.position.x = pos.x;
  firework.position.y = pos.y + yOffset;
  firework.width = size;
  firework.height = size;
  firework.image = text;
  fireworks.push(firework);
  stage.addChild(firework);
}
// fireWork.js
function fireWorksShowBegin() {
  //...
  requestAnimationFrame(fireWorksAnimate);
}
function fireWorksAnimate() {
  requestAnimationFrame(fireWorksAnimate);
  // 将对象渲染到其 WebGL 视图。
  renderer.render(stage);
}
  • 我们来看看现在的效果吧

烟花发射

  • 现在最终需要呈现的效果已经做好了,我们只需要在循环动画fireWorksAnimate方法中将每一个图标都慢慢发射到对应的像素点即可。
  • fireWorksAnimate中我们首先需要做一件事情,那就是将图标的位置从一个地方位移到我们真正想要烟花爆炸💥的位置,但是在这之前我们已经将图标放到爆炸的位置了,现在我们需要对初始化创建的图标做处理重新给定一个起点记录爆炸的位置让他缓慢位移。
  • 我们需要改动createEmojiFirework方法,设置起点position(x,y)和终点explodePositio(x,y)并设置图标移动速度speed
// fireWork.js
function createEmojiFirework(text, pos, i) {
  //...
  // 记录最终爆炸的位置
  firework.explodePosition = {
    x: pos.x,
    y: pos.y + yOffset,
  };
  //给图标一个起点位置
  firework.position.x = 20;
  firework.position.y = 30;
  //设置图标移动速度
  firework.speed = 0.02
}
  • 而在fireWorksAnimate中我们需要计算从起点到终点的每次位移。
// fireWork.js
function fireWorksAnimate() {
  for (let i = 0; i < fireworks.length; i++) {
    fireworks[i].position.x +=
      (fireworks[i].explodePosition.x - fireworks[i].position.x) *
      fireworks[i].speed;
    fireworks[i].position.y +=
      (fireworks[i].explodePosition.y - fireworks[i].position.y) *
      fireworks[i].speed;
  }
}
  • 现在的效果是这样的

  • 好家伙效果是移动了,但是太整齐了,我们在createEmojiFirework中给烟花一个定时器让他延迟一个一个的发射,这下效果就好很多了。

烟花爆炸

  • 烟花发射成功了,接下来我们要完善烟花爆炸的效果。
  • 爆炸的原理其实也是跟生成图标差不多。
    • 在每一次的动画中循环中我们都要计算图标是否快要到达终点。
    • 如果快要到达就给它一个标记并触发爆炸事件。
    • 在爆炸事件中我们需要生成多个粒子,给每个粒子初始化不同的x轴y轴的位移速度,并且记录大小和位置。
// fireWork.js
let particles = [];
function fireWorksAnimate() {
  //...
  for (let i = 0; i < fireworks.length; i++) {
  //...
    if (!fireworks[i].exploded) {
      //计算是否快要到达终点
      if (
        Math.abs(fireworks[i].position.x - fireworks[i].explodePosition.x) +
          Math.abs(fireworks[i].position.y - fireworks[i].explodePosition.y) <
        100
      ) {
        fireworks[i].exploded = true;
        explodeFirework(fireworks[i]);
      }
    }
  }
 //...
}
function explodeFirework(firework) {
  for (let i = 0; i < 20; i++) {
    const size = 20;
    let particle = new PIXI.Sprite(firework.image);
    particle.speed = {
      x: (Math.random() - 0.5) * (Math.random() * 10),
      y: (Math.random() - 0.5) * (Math.random() * 10),
    };
    particle.position.x = firework.position.x;
    particle.position.y = firework.position.y;
    particle.width = size;
    particle.height = size;
    particles.push(particle);
    stage.addChild(particle);
  }
}
  • 至此每个图标烟花就会在到达终点后生成20个相同大小的例子,但是这个例子一直存在于画布中,我们需要计算他的位移产生一种爆炸的效果。
  • 在每次画布重新渲染中我们将每一个粒子都按照它自身的速度分别在xy位移,并设置alpha不透明度递减。
// fireWork.js
function fireWorksAnimate() {
  //...
  for (let i = 0, l = particles.length; i < l; i++) {
      particles[i].position.x += particles[i].speed.x;
      particles[i].position.y += particles[i].speed.y;
      particles[i].speed.y += 0.03;
      particles[i].alpha -= 0.01;
    }
 //...
}
  • 最后呈现的效果就是这样啦~~

  • 但是现在的效果太统一了,如果当我们将发射位置 终点位置 发射速度 爆炸大小全部随机之后会发生什么呢?

最终效果

  • 好啦这样想要的效果就制作完成了,接下来就加上小卢给大家的新年贺语就结束了(贺卡有彩蛋喔~😆)。
  • 源码地址   
  • 展示地址(请在PC端打开喔~)

👋 写在最后

  • 首先还是很感谢大家看到这里,这次的文章就分享到这里,提前祝大家春节快乐!!
  • 在新的一年希望大家越来越好,顺顺利利,疫情早日过去!!!!
  • 如果您觉得这篇文章有帮助到您的的话不妨🍉🍉关注+点赞+收藏+评论+转发🍉🍉支持一下哟~~😛您的支持就是我更新的最大动力。
  • 如果想跟我一起讨论和学习更多的前端知识可以加入我的前端交流学习群,大家一起畅谈天下~~~

🌅 往期精彩

「前端该如何优雅地Mock数据🏃」每个前端都应该学会的技巧

那个一年前找不到工作的男人,2021年怎么样了?

请务必收下这10+个加载特效,保证让你的项目大放异彩⭐

我为大家带来了10张“科技感满满”的可视化数据大屏⚡,收藏等于学会~