<div id="app">
  <div id="screen" v-on:click="screenClick">
    <test-explosion v-for="testExplosion, i in testExplosions"
                    v-bind:test-explosion="testExplosion"
                    v-bind:explosion-index="i" />
  </div>
</div>
<!-----TEMPLATE----->
<script type="text/x-template" id="tplTestExplosion">
  <div class="explosion"
       ref="explosion"
       v-bind:style="testExplosion">
    <div class="fire"
         v-for="fire in fires"
         v-bind:style="fire"></div>
  </div>
</script>
body {
  overflow: hidden;
}

#screen {
  background-image: linear-gradient(to bottom, #090024, #500fc8);
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

.explosion {
  position: absolute;
}

.fire {
  position: absolute;
}

.fire:before {
  content: '';
  background-image: radial-gradient(#ff8f0f 0% 40%, #ffea00 100%);
  width: 100%;
  height: 100%;
  display: block;
  box-sizing: border-box;
  border-radius: 50%;
  box-shadow: 0 0 7px #ffea00;
  animation: fire 100ms infinite;
}

@keyframes fire {
  0% {
    transform: scale(1);
  }
  100% {
    transform: scale(1.2);
  }
}
"use strict";

Vue.component("test-explosion", {
  template: "#tplTestExplosion",
  props: ["testExplosion", "explosionIndex"],
  data(): object {
    return {
      fires: [],
      count: {
        start: 50,
        end: 100,
      },
      // Distance X
      moveX: {
        start: 0,
        end: 220,
      },
      // Distance Y
      moveY: {
        start: 0,
        end: 280,
      },
      jumpY: {
        start: 80,
        end: 120,
      },
      duration: {
        start: 1.5,
        end: 2.5,
      },
      blur: {
        start: 1,
        end: 3
      },
      size: [10, 12.5, 15, 17.5, 20, 22.5, 25, 27.5, 30, 32.5, 35, 37.5, 40, 42.5, 45, 47.5, 50],
    }
  },
  methods: {
    // Generates random number
    // start : Minimum value
    // end   : Maxumum value
    // round : Round return value
    randomize(start: number, end: number, round: boolean = true): number {
      if (round === true) {
        return Math.round(Math.random() * (end - start) + start);
      }
      else {
        return Math.random() * (end - start) + start;
      }
    },
    // Randomize whether the number should be negative or positive
    // Number to be checked
    positiveOrNegative(value: number): number {
      return (this.randomize(0, 1) === 1) ? -value : value;
    },
    // When the explosion has ended
    // Index of the element of the explosion
    endExplode(): void {
      this.$el.parentNode.removeChild(this.$el);
    },
    // Creates fires for explosion
    createFires(): void {
      let count: number = this.randomize(this.count.start, this.count.end);

      for (let i: number = 0; i < count; i++) {
        let rand: number = this.randomize(0, 4);
        let size: number = this.size[rand];

        this.fires.push({
          width: size + "px",
          height: size + "px",
          top: (-size / 2) + "px",
          left: (-size / 2) + "px",
        });
      }
    },
    // Move the fire from one location to another, using GSAP
    animateFires(): void {
      let explosion: object = this.$refs.explosion;
      let fires: object = explosion.querySelectorAll(".fire");
      let duration: number = this.randomize(
        this.duration.start, this.duration.end
      );
      
      for (let i: number = 0; i < fires.length; i++) {
        let fire: object = fires[i];
        
        let x: number = this.randomize(this.moveX.start, this.moveX.end);
        let y: number = this.randomize(this.moveY.start, this.moveY.end);
        let j: number = this.randomize(this.jumpY.start, this.jumpY.end);
        let a: number = j / 100;
        let b: number = 1 - a;
        let f: number = this.randomize(this.blur.start, this.blur.end);
        
        let timeline: object = gsap.timeline();
        
        timeline.to(fire, {
          duration: duration,
          x: this.positiveOrNegative(x),
          opacity: 0,
          filter: "blur(" + f + "px)"
        }, 0).to(fire, {
          duration: duration * a,
          ease: "power2.out",
          y: -y * a,
        }, 0).to(fire, {
          duration: duration * b,
          ease: "power2.in",
          y: y * b,
        }, duration * a);
      }
    },
  },
  // mounted() hook
  mounted(): void {
    this.createFires();
  },
  // updated() hook
  updated(): void {
    this.animateFires();
  },
});

let app: object = new Vue({
  el: "#app",
  data: {
    testExplosions: []
  },
  methods: {
    // Event when the screen is clicked
    // e : Event object
    screenClick(e): void {
      this.testExplosions.push({
        top: e.clientY + "px",
        left: e.clientX + "px",
      });
    }
  },
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js