Skip to content

Instantly share code, notes, and snippets.

Created November 12, 2014 11:49
Show Gist options
  • Save anonymous/b57349b840737e5ccd6c to your computer and use it in GitHub Desktop.
Save anonymous/b57349b840737e5ccd6c to your computer and use it in GitHub Desktop.
A Pen by Ashley Kyd.

DOM particle emitter

A particle emitter using DOM elements and CSS transforms instead of more sensible technologies like SVG or Canvas.

A Pen by Ashley Kyd on CodePen.

License.

<div id="box">
<h1>DOM particle emitter</h1>
<p>So this is kinda neat I guess, it's a particle emitter using DOM elements instead of more sensible technologies like SVG or Canvas.</p>
<p>For bonus points/performance, this has been updated to use CSS transitions rather than requestAnimationFrame.</p>
<p>Click for an explosion.</p>
</div>
var emitters = [];
// http://davidwalsh.name/vendor-prefix
var prefix = (function () {
var styles = window.getComputedStyle(document.documentElement, ''),
pre = (Array.prototype.slice
.call(styles)
.join('')
.match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
)[1],
dom = ('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1];
return {
dom: dom,
lowercase: pre,
css: '-' + pre + '-',
js: pre[0].toUpperCase() + pre.substr(1)
};
})();
var Emitter = function(opts){
this.particles = [];
this.opts = opts;
this.total = 0;
this.opts.i = Date.now();
this.particleType = opts.particleType || Particle;
this.ele = document.createElement('div');
this.ele.className = 'emitter';
this.ele.setAttribute('aria-hidden','true');
this.ele.setAttribute('style', 'position:absolute;transform:translate('+this.opts.center[0]+'px, '+this.opts.center[1]+'px);');
for(var i=0; i<opts.particles;i++){
this.total++;
this.particles.push(new this.particleType(this.ele, opts));
}
document.body.appendChild(this.ele);
}
Emitter.prototype.destroy = function(){
this.ele.parentNode.removeChild(this.ele);
}
var Particle = function(parent, opts){
// this.ele = this.pool.pop(); // Broken
if(!this.ele){
this.ele = document.createElement('div');
this.ele.setAttribute('style','position:absolute;');
this.ele.className = 'particle';
}
parent.appendChild(this.ele);
this.reset(opts);
var _this = this;
this.ele.addEventListener( prefix.lowercase+'TransitionEnd',
function( event ) {
if(opts.loop){
_this.reset(opts, 0);
} else {
console.log('cleaning up')
if(_this.ele.parentElement){
_this.ele.parentElement.removeChild(_this.ele);
}
_this.pool.push(_this.ele);
}
}, false );
}
// Save old particles here because creating them is OMG EXPENSIVE.
Particle.prototype.pool = [];
Particle.prototype.beforeDraw = function(p){
this.ele.style.opacity = p;
}
Particle.prototype.reset = function(opts, p){
if(opts.colorFn){
this.color = opts.colorFn();
} else {
this.color = opts.color || 'white';
}
this.r = this.fuzz(opts.r) || 4;
this.ang = this.fuzz(opts.ang) || Math.PI*2*Math.random();
this.spd = this.fuzz(opts.spd) || Math.random()/5;
this.life = this.fuzz(opts.life) || 250+Math.random() * 250;
this.i = opts.i || 0;
this.animate = opts.animate || ['scale'];
this.rNow = this.r;
this.angNow = Math.PI/2+ this.ang;
this.spdNow = this.spd;
this.colorNow = this.color;
this.draw(0,1);
var _this = this;
window.setTimeout(function(){
_this.draw(_this.life,0);
});
}
Particle.prototype.fuzz = function(value){
if(!value){
return false;
}
return value[0] + (value[1]*2*Math.random()-value[1]);
}
Particle.prototype.draw = function(i,p){
if(p===1){
this.ele.classList.add('notransition');
} else {
this.ele.style[prefix.lowercase+'TransitionDuration'] = this.life/1000+'s';
this.ele.classList.remove('notransition');
}
this.beforeDraw(p);
var radiusOffset = (0-this.rNow/2);
this.ele.style.transform = [
'translate('+radiusOffset+'px,'+radiusOffset+'px)', // Center on circle
'rotate('+this.angNow+'rad)', // Rotate in whichever direction we're emitting.
'translate('+(i*this.spdNow)+'px,0)' // Move however far we need to go.
].join(' ');
this.ele.style.width = this.rNow+'px';
this.ele.style.height = this.rNow+'px';
this.ele.style.backgroundColor = this.colorNow;
this.ele.style.borderRadius = this.rNow+'px';
}
var ParticleShrink = function(parent, opts){
Particle.call(this, parent, opts);
}
for(var i in Particle.prototype){
ParticleShrink.prototype[i] = Particle.prototype[i];
}
ParticleShrink.prototype.beforeDraw = function(p){
this.rNow = this.r * p;
}
function explode(x,y){
emitters.push(new Emitter({
particles:5,
particlesTotal:10,
colorFn:function(){
var color = 'rgba(128,128,128,'+Math.random()*.5+')';
return color;;
},
ang: [Math.PI,Math.PI],
r: [25,10],
spd: [.03,.02],
life: [1000,800],
center: [x,y]
}));
emitters.push(new Emitter({
particles:20,
particlesTotal:10,
colorFn:function(){
var color = 'rgba(255,0,0,'+Math.random()*.5+')';
return color;;
},
ang: [Math.PI,Math.PI],
r: [16,6],
spd: [.1,.05],
life: [800,250],
center: [x,y]
}));
emitters.push(new Emitter({
particles:15,
particlesTotal:15,
colorFn:function(){
var color = 'rgba(255,255,0,1)';
return color;;
},
ang: [Math.PI,Math.PI],
r: [5,3],
spd: [.2,.05],
life: [600,250],
center: [x,y]
}));
}
function flames(x,y){
emitters.push(new Emitter({
particles:40,
particlesTotal:-1,
color:'rgba(90,90,90,.5)',
ang: [Math.PI,.5],
r: [10,8],
spd: [.01,.005],
life: [5000,5000],
center: [x,y],
loop:true
}));
/* Engine Flames */
emitters.push(new Emitter({
particles:40,
particlesTotal:-1,
color:'red',
ang: [Math.PI,.5],
r: [8,3],
spd: [.02,.0025],
life: [2000,2000],
center: [x,y],
loop:true
}));
emitters.push(new Emitter({
particles:60,
particleType: ParticleShrink,
particlesTotal:-1,
color:'#f40',
ang: [Math.PI,.3],
r: [5,2],
spd: [.04,.01],
life: [3000,1000],
center: [x,y],
loop:true
}));
emitters.push(new Emitter({
particles:40,
particleType: ParticleShrink,
particlesTotal:-1,
color:'orange',
ang: [Math.PI,.25],
r: [4,2],
spd: [.02,.01],
life: [3000,300],
center: [x,y],
loop:true
}));
emitters.push(new Emitter({
particles:40,
particlesTotal:-1,
color:'rgba(255,255,255,.5)',
ang: [Math.PI,.25],
r: [3,2],
spd: [.02,.01],
life: [2000,300],
center: [x,y],
loop:true
}));
}
flames(200,300);
document.body.onclick = function(e){
explode(e.clientX,e.clientY);
}
document.body.style.height=window.innerHeight+'px';
body{
background:black;
color:white;
margin:0;
padding:0;
font-family:sans-serif;
}
#box{
position:absolute;
max-width:400px;
padding:20px;
}
.particle{
transition: all 1s;
}
.notransition {
transition: none !important;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment