<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
This Pen doesn't use any external CSS resources.