🔥「新春烟火」 释放五彩文字烟花🎇,祝大家新年快乐🎉

1,633 阅读6分钟

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

生活不止眼前的苟且,还有诗和远方

掘友们,大家好我是前端奶爸,入行5年的前端小学生🥜~
工作八小时摸鱼四小时,喜欢跑步但不是为了让自己更瘦,而是为了让自己活得更久~
活到九十九,卷到九十九~

前言

老话说的好,过了腊八就是年🥢

马上就要过年了,小伙伴们都躁动不已🔥

平台为了烘托年味,发布了新年活动,我也参加一波~🎉🎉🎉

论过年最有意思的事,我觉得第一想到的就是放鞭炮,小的时候家里穷,鞭炮也只能买几鞭,还要分给不同的时间进行正常仪式燃放,留给孩子们玩的会很少。

所以家里买鞭炮以后,会从大挂上一个个拆下来,然后拿一根粗香。年三十一大早换上新衣服,和几个小伙伴一起出去玩,左边裤兜里装满糖果(PS:只有过年这几天可以实现糖果自由),右边的裤兜自然就是鞭炮了,我们会去村里枯井,水塘,还有各种地方去做一些有意思的爆炸试验,还会拿着鞭炮去吓唬小女孩们(PS:尽量不要这么做)

现在过年的炮声是越来越少了,前几年的北京五环外还可以燃放,但是从今年开始也全面禁止了,现在的村里有些地方不烧煤,政府提供统一供暖。村里大部分也都是天气管道,所以也不让燃放烟花,过年的年味感觉缺失了不少。

还记得去年朵朵小朋友第一次见到烟花兴奋惊讶的样子,盯着窗外目不转睛,今年政府不让燃放烟花,那就用 canvas 简单的做一个烟花吧~ 希望她会喜欢~💗

具体实现

画布创建

html中首先创建一个 canvas 元素

<body>
  <canvas id="canvas"></canvas>
  <script src="http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js">
  <script src="./index.js"></script>
</body>
  • 为了节约时间,直接引入了 jquery 开发获取 dom 以及事件绑定操作。

初始化画布

  • 获取 canvas 对象并且赋值宽高为满屏幕模式。
  • 通过监听 windowsresize 动态修改 canvas 的宽高,以及重点位置的具体坐标。
  • 初始化画布,设置画布初始属性。
const canvas = $("#canvas")[0];
canvas.width = $(window).width();
canvas.height = $(window).height();
const ctx = canvas.getContext("2d");

// 监听窗口改变
$(window).on("resize", function () {
canvas.width = $(window).width();
canvas.height = $(window).height();
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
center = { x: canvas.width / 2, y: canvas.height / 2 };
});

// 初始化画布
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);

定义绘制素材

  • 定义绘制文字的内容字符串。
  • 初始化位置数组,把文字在画布进行布局排列。
  • 绘制字母的矩阵位置数组创建。
// 初始化基础对象
const listText = []; // 画布填充粒子数组
const fireNumber = 10; // 初始化粒子基数
const center = { x: canvas.width / 2, y: canvas.height / 2 }; // 当前画布中心位置
const range = 100; // 粒子动画范围
let textIndex = 0; // 粒子动画索引

// 定义绘制文字内容
const textString = "happynewyear2022";

// 初始位置数组
const textMatrix = [
    4.5, 0, 5.5, 0, 6.5, 0, 7.5, 0, 8.5, 0,
    3, 1, 4, 1, 5, 1, 7, 1, 8, 1, 9, 1, 10, 1,
    5, 2, 6, 2, 7, 2, 8, 2,
];

// 字母矩阵位置
const chars = {
    h: [
      0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 1, 3, 2, 3, 3, 3, 4, 3, 5,
      0, 5, 1, 5, 2, 5, 3, 5, 4, 5, 5, 5, 6, 5, 7,
    ],
    a: [
      2, 0, 2, 1, 2, 2, 1, 2, 1, 3, 1, 4, 1, 5, 0, 5, 0, 6, 0, 7, 2, 5, 3, 0, 3,
      1, 3, 2, 4, 2, 4, 3, 4, 4, 4, 1, 5, 5, 5, 6, 5, 7, 3, 5,
    ],
    p: [
      0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 1, 0, 2, 0, 3, 0, 4, 1, 5,
      2, 4, 3, 3, 4, 2, 4, 1, 4,
    ],
    y: [
      0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 3, 5, 3, 6, 3, 7, 4, 3, 4,
      2, 5, 2, 5, 1, 6, 1, 6, 0,
    ],

    n: [
      0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 1, 1, 1, 2, 2, 2, 2, 3, 2,
      4, 3, 4, 3, 5, 4, 5, 4, 6, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 5, 5, 5, 6, 5, 7,
    ],
    e: [
      0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 1, 0, 2, 0, 3, 0, 4, 0, 5,
      0, 1, 3, 2, 3, 3, 3, 4, 3, 1, 7, 2, 7, 3, 7, 4, 7, 5, 7,
    ],
    w: [
      0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 6, 1, 7, 2, 7, 3, 7, 3.5, 6,
      3.5, 5, 3.5, 4, 3.5, 3, 3.5, 2, 3.5, 1, 4, 7, 5, 7, 6, 7, 6, 6, 7, 6, 7,
      5, 7, 4, 7, 3, 7, 2, 7, 1, 7, 0,
    ],
    r: [
      0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 1, 0, 2, 0, 3, 0, 4, 1, 5,
      2, 4, 3, 3, 4, 2, 4, 1, 4, 1, 5, 2, 5, 3, 6, 4, 6, 5, 7,
    ],
    0: [
      0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 0, 2, 0, 3, 0, 4, 0, 1, 7, 2, 7, 3,
      7, 4, 7, 5, 1, 5, 2, 5, 3, 5, 4, 5, 5, 5, 6,
    ],
    2: [
      0, 0, 0, 1, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 5, 1, 5, 2, 5, 3, 4, 3, 3, 3, 2,
      3, 2, 4, 1, 4, 1, 5, 0, 5, 0, 6, 0, 7, 1, 7, 2, 7, 3, 7, 4, 7, 5, 7, 5, 6,
    ],
};

具体的字母坐标如何绘制,看下图的详细说明,以数字 2 和字母 Y为例:

WechatIMG105875.png

数组按两位为一个单位,根据横纵坐标的位置,大概绘制出如下字母和数字符号。

WechatIMG105885.png

具体绘制函数

具体的实现逻辑可拆分为5️⃣个函数:

  • 初始化粒子文字函数
  • 更新运动函数
  • 绘制内容函数
  • 循环动画执行函数
  • 随机颜色生成函数

初始化粒子文字函数

  • 判断 循环索引 i 长度在 textString 长度内。
  • 生成随机 velocity 参数。
  • 根据 textMatrix 内对应内容的位置初始化多个粒子对象。
  • listText 填充生成的粒子对象。
  • 通过索引递增判断是否填充完毕,未填充完毕继续执行填充。
function initText() {
    const i = textIndex;
    if (i >= 0 && i < textString.length) {
      const velocity = Math.random() * 0.25 + 1;
      const shift = { x: -(Math.random() + 2), y: -(Math.random() + 3) };
      const char = chars[textString[i]];
      const width = 80;
      const half = 6.5 * width;
      const left = textMatrix[i * 2] * width - half;
      const top = textMatrix[i * 2 + 1] * range * 1.2 - range * 2.4;
      for (let j = 0; j < fireNumber * char.length * 0.25; j++) {
        const rand = Math.floor(Math.random() * char.length * 0.5);
        const x = char[rand * 2] + shift.x;
        const y = char[rand * 2 + 1] + shift.y;
        const text = {
          x: center.x + left * 0.9,
          y: center.y + top,
          left: center.x + left,
          size: Math.random() + 0.5,
          fill: randColor(),
          vx: x * (velocity + (Math.random() - 0.5) * 0.5),
          vy: y * (velocity + (Math.random() - 0.5) * 0.5),
          ay: 0.08,
          alpha: 1,
          life: Math.round((Math.random() * range) / 2) + range / 1.5,
        };
        text.base = {
          life: text.life,
          size: text.size,
        };
        text.direct = (text.left - text.x) * 0.08;
        listText.push(text);
      }
    }

    if (++textIndex < textString.length) {
      setTimeout(initText, 10);
    }
  }

更新运动函数

  • 循环 listText 粒子动画对象。
  • 更新数组内每一项的运动轨迹,生命周期,以及大小变化。
  • 每一项生命周期递减。
  • 判断生命周期是否结束,如果结束清除该项。
  • 更新粒子文字函数
function update() {
    // 更新listText运动
    for (let i = listText.length - 1; i >= 0; i--) {
      const text = listText[i];
      text.vx *= 0.9;
      text.vy *= 0.9;
      text.direct *= 0.9;
      text.x += text.vx + text.direct;
      text.y += text.vy;
      text.vy += text.ay;
      text.alpha = text.life / text.base.life;
      text.size = text.alpha * text.base.size;
      text.alpha = text.alpha > 0.6 ? 1 : text.alpha;
      // 生命周期递减
      text.life--;
      if (text.life <= 0) {
        listText.splice(i, 1);
      }
    }

    initText();
}

绘制内容函数

  • 每次绘制前清空画布。
  • 设置新图像绘制目标图像具体属性。
  • 按照初始绘制内容进行 canvas 绘制。
function draw() {
    // 绘制前清空
    ctx.globalCompositeOperation = "source-over";
    ctx.globalAlpha = 0.2;
    ctx.fillStyle = "#000";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 设置新图像绘制目标图像具体属性
    ctx.globalCompositeOperation = "screen";

    // 绘制: HAPPY NEW YEAR 2020!
    for (let i = 0; i < listText.length; i++) {
      const text = listText[i];
      ctx.globalAlpha = text.alpha;
      ctx.fillStyle = text.fill;
      ctx.fillRect(
        text.x - text.size,
        text.y - text.size,
        text.size * 2,
        text.size * 2
      );
    }
}

循环动画执行函数

  • 使用 requestAnimationFrame 方法执行循环函数。
(function loop() {
    requestAnimationFrame(loop);
    update();
    draw();
})();

随机颜色生成函数

最后这个函数其实就比较简单了,为了生成一个彩色的各节点封装了一个随机生成 rgb 颜色的公共方法。

function randColor() {
    const r = Math.floor(Math.random() * 256);
    const g = Math.floor(Math.random() * 256);
    const b = Math.floor(Math.random() * 256);
    const color = `rgb(${r}, ${g}, ${b})`;
    return color;
}

总结

到这里所有的代码内容就结束了,由于时间有限,就不一行一行分析,大家可以通过复制上方代码片段进行实际效果预览和修改。

有更好的想法和思路也欢迎大家在评论区留言,码字不易,看到的朋友们如果绝我的写的内容对你有一点点启发,希望不要吝惜点赞,你们的鼓励就是我写作的动力!

在这里提前给各位拜年了!🔥

祝大家:

工作顺利,身体健康,万事如意,幸福美满!

🧨 新年快乐,🐯 虎虎生威!