トップへ(mam-mam.net/)

Draw Fireworks Animation in JavaScript — Canvas‑Based Particle Effects (Copy & Paste Ready)

Japanese

This page explains how to create a fireworks animation on a webpage using JavaScript and Canvas.
It includes copy‑and‑paste‑ready code that lets you implement a full‑featured fireworks effect, featuring radial particle bursts drawn with sin/cos, glowing gradients, and multi‑stage explosions.
Perfect for summer‑themed event pages or any seasonal visual effects.

HTML,Javascript

<canvas class="Hanabi" style="width:100%;max-width:800px;height:200px;"></canvas>

<script>
TMamHanabi = function(){
    this.can = [];
    this.ctx = [];
    this.width = [];
    this.height = [];
    this.position = [];
    this.basewh = [];
    this.basesp = [];
    this.limit = [];
    this.flag = [];
    this.child = [];

    this.init = function(){
      this.can = document.querySelectorAll(".Hanabi");
      for(let i = 0; i < this.can.length; i++){
        this.ctx[i] = this.can[i].getContext("2d", {willReadFrequently:true});

        // Get the displayed size of the canvas
        let style = window.getComputedStyle(this.can[i]);
        this.width[i] = parseInt(style.width);
        this.height[i] = parseInt(style.height);
        this.basewh[i] = Math.min(this.width[i], this.height[i]);
        this.basesp[i] = this.height[i] / 200;

        // Set the internal canvas size
        this.can[i].setAttribute("width", this.width[i] + "px");
        this.can[i].setAttribute("height", this.height[i] + "px");

        this.position[i] = [];
        this.position[i]["x"] = this.width[i] / 2;
        this.position[i]["y"] = this.height[i];

        // Random explosion height
        this.limit[i] = this.height[i] * 1/3 + Math.random() * this.height[i] / 3 - this.height[i] / 6;

        this.flag[i] = 0;
        this.child[i] = [];

        this.reset(i);

        // Fill background
        this.ctx[i].fillStyle = "#000";
        this.ctx[i].fillRect(0, 0, this.width[i], this.height[i]);
      }
      window.addEventListener("resize", this.resize.bind(this));
    }

    this.resize = function(){
      for(let i = 0; i < this.can.length; i++){
        // Get displayed size again on resize
        let style = window.getComputedStyle(this.can[i]);
        this.width[i] = parseInt(style.width);
        this.height[i] = parseInt(style.height);
        this.basewh[i] = Math.min(this.width[i], this.height[i]);
        this.basesp[i] = this.height[i] / 200;

        // Update internal canvas size
        this.can[i].setAttribute("width", this.width[i] + "px");
        this.can[i].setAttribute("height", this.height[i] + "px");
      }
    }

    this.reset = function(i){
      // Reset launch position and explosion height
      this.position[i]["x"] = this.width[i] / 3 + this.width[i] * Math.random() / 3;
      this.position[i]["y"] = this.height[i];
      this.limit[i] = this.height[i] * 1/3 + Math.random() * this.height[i] / 3 - this.height[i] / 6;
      this.flag[i] = 0;

      // Generate child particles
      let ct = 0;
      for(let j = 0; j < 12; j++){
        for(let k = 0; k < (j + 1) * 8; k++){
          let deg = 360 / ((j + 1) * 8);
          this.child[i][ct] = [];
          this.child[i][ct]["x"] = this.position[i]["x"];
          this.child[i][ct]["y"] = this.limit[i];

          let rd = Math.random();
          this.child[i][ct]["vx"] =
            Math.cos(deg * k * Math.PI / 180) *
            (j + 0.8 + rd * Math.random() * 0.4) *
            this.basewh[i] / 640 / 5;

          this.child[i][ct]["vy"] =
            Math.sin(deg * k * Math.PI / 180) *
            (j + 0.8 + rd * Math.random() * 0.4) *
            this.basewh[i] / 640 / 5;

          // Randomized color
          let rr = Math.floor(Math.random() * j / 2 + 255 - j * 2);
          let gg = Math.floor(Math.random() * j / 4 + 255 - j * 8);
          let bb = Math.floor(Math.random() * j / 16 + 255 - j * 16);
          this.child[i][ct]["c"] = '' + rr + ',' + gg + ',' + bb + '';

          ct++;
        }
      }
    }

    this.draw = function(){
      for(let i = 0; i < this.ctx.length; i++){
        // Clear background
        this.ctx[i].fillStyle = "#000";
        this.ctx[i].fillRect(0, 0, this.width[i], this.height[i]);

        // Launch phase
        if(this.flag[i] == 0){
          if(this.position[i]["y"] < this.limit[i]){
            this.flag[i] = 1; // Switch to explosion phase
          }else{
            this.position[i]["y"] -= this.basesp[i];
          }

          // Draw rising light
          let x = this.position[i]["x"];
          let y = this.position[i]["y"];
          let r = this.basewh[i] / 80 + Math.random() * this.basewh[i] / 80;

          var rgrd = this.ctx[i].createRadialGradient(x, y, r, x, y, 0);
          let rr = Math.floor(Math.random() * 5 + 250);
          let gg = Math.floor(Math.random() * 55 + 200);
          let bb = Math.floor(Math.random() * 55 + 200);

          rgrd.addColorStop(0.0, 'rgba(' + rr + ',' + gg + ',' + bb + ',0.0)');
          rgrd.addColorStop(0.2, 'rgba(' + rr + ',' + gg + ',' + bb + ',0.2)');
          rgrd.addColorStop(1.0, 'rgba(' + rr + ',' + gg + ',' + bb + ',0.8)');

          this.ctx[i].beginPath();
          this.ctx[i].arc(x, y, r, 0, 2 * Math.PI);
          this.ctx[i].fillStyle = rgrd;
          this.ctx[i].fill();

        // Explosion phase
        }else if(this.flag[i] > 0){
          if(this.flag[i] > 100){
            this.flag[i] = -50;
            return;
          }

          var r;
          for(let j = 0; j < this.child[i].length; j++){
            r = (1 + Math.random() / 2) * this.basewh[i] / 128;

            // Gravity
            this.child[i][j]["vy"] += this.basewh[i] / 160 / 200 * (1 + Math.random() / 8);

            // Movement
            this.child[i][j]["x"] += this.child[i][j]["vx"];
            this.child[i][j]["y"] += this.child[i][j]["vy"];

            // Particle glow
            let rgrd = this.ctx[i].createRadialGradient(
              this.child[i][j]["x"], this.child[i][j]["y"], r,
              this.child[i][j]["x"], this.child[i][j]["y"], 0
            );

            let op = (100 - this.flag[i]) / 100;

            rgrd.addColorStop(0.0, 'rgba(' + this.child[i][j]["c"] + ',' + 0.0 * op + ')');
            rgrd.addColorStop(0.6, 'rgba(' + this.child[i][j]["c"] + ',' + 0.8 * op + ')');
            rgrd.addColorStop(1.0, 'rgba(' + this.child[i][j]["c"] + ',' + 1.0 * op + ')');

            this.ctx[i].beginPath();
            this.ctx[i].arc(this.child[i][j]["x"], this.child[i][j]["y"], r, 0, 2 * Math.PI);
            this.ctx[i].fillStyle = rgrd;
            this.ctx[i].fill();
          }
          this.flag[i]++;

        // Cooldown phase
        }else{
          this.flag[i]++;
          if(this.flag[i] == 0){
            this.reset(i);
          }
        }
      }
    }

    window.addEventListener("DOMContentLoaded", this.init.bind(this));
    setInterval(this.draw.bind(this), 20);
}
MamHanabi = new TMamHanabi();
</script>