Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <canvas id="myCanvas"></canvas>
<h1>Simple Particle Engine</h1>
<p>Move your mouse around :-) By Jason Mayes 2014</p>
<a id="linkage" href="http://www.jasonmayes.com" target="_blank">View my website</a>

<div id="effectChanger">
  <h3>Change particle effect</h3>
  <p>Choose from some presets below, but feel free to experiment too by changing the example code towards end of JS! Let me know about any cool effects you invent using this: @jason_mayes!</p>
  <select id="changer">
    <option value="1">Fire using images</option>
    <option value="2">Autumn Fire Blocks</option>
    <option value="3">Rain</option>
    <option value="4">Smokey Blocks</option>
    <option value="5">Small random colour particles</option>
  </select>
</div>
              
            
!

CSS

              
                body {
  background-color:#1d1d1d;
  margin:0;
  padding:0;
}

canvas {
  position:fixed;
  top:0;
  left:0;
  right:0;
  bottom:0;
}

#linkage {
  position:fixed;
  top:145px;
  right:0px;
  background-color:#2d2d3d;
  color:#ffffff;
  text-decoration:none;
  padding:10px;
  width:100px;
}

h1 {
  text-align:center;
  margin-top:50px;
}

h1, p, h2, a, h3 {
  font-family:'arial';
  color:#efefef;
}

p {
  font-size:10pt;
  font-style:italic;
  color:#686868;
  text-align:center;
}

#effectChanger {
  position:fixed;
  bottom:20px;
  right:20px;
  width:250px;
}

#effectChanger p {
  text-align:left;
}
              
            
!

JS

              
                /*********************************************************************
*  #### Simple Particle Engine v3.0 (Work in progress!) ####
*  Coded by Jason Mayes 2014. www.jasonmayes.com
*  Please keep this disclaimer with my code if you use it. Thanks. :-)
*  Got feedback or questions, ask here:
*  https://plus.google.com/+JasonMayes/posts
 * Updates will be posted to this site:
 * http://www.jasonmayes.com/projects/particleEngine/
 * Or see Github:
 * https://github.com/jasonmayes/Particle-Engine
*********************************************************************/

// Polyfill for requestAnimationFrame.
window.requestAnimationFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          window.oRequestAnimationFrame      ||
          window.msRequestAnimationFrame     ||
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();


/* The magical particle engine! */
var jmParticleEngine = function() {
  // Reference to the canvas DOM element.
  var canvas = null;

  // Hold canvas context.
  var ctx = null;

  // Hold all emitters.
  var emitters = [];

  /**
   * A class which allows us to create Particle instances.
   * @param {Number} x Starting x co-ordinate of the particle.
   * @param {Number} y Starting y co-ordinate of the particle.
   * @param {Number} w Width of particle.
   * @param {Number} h Height of particle.
   * @param {Number} rot Rotation in radians of the particle.
   * @param {Number} xVel Velocity in the x direction.
   * @param {Number} yVel Velocity in the y direction.
   * @param {Number} life How long will the particle last for in animation frames.
   * @param {Number} s How will particle size change with life? 0 = no change,
   *    1 = smaller, 2 = larger. Taken as a % of width / height.
   * @param {Number} r The amount of red in the particle (0 - 255).
   * @param {Number} g The amount of green in the particle (0 - 255).
   * @param {Number} b The amount of blue in the particle (0 - 255).
   * @param {Number} i Optional. The index of a preloaded image we wish to
   *     render.
   */
  function Particle(x, y, w, h, rot, xVel, yVel, life, s, r, g, b, i) {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    this.rotation = rot;
    this.xVelocity = xVel;
    this.yVelocity = yVel;
    this.maxLife = life;
    this.life = life;
    this.growthType = s;
    this.r = r;
    this.g = g;
    this.b = b;
    this.image = i;
  }

  /**
   * A class which allows us to create Emitters of particles.
   * @param {Number} x The x co-ordinate of the emitter location.
   * @param {Number} y The y co-ordinate of the emitter location.
   * @param {Number} m  Enforce a hard limit on the number of displayable
   *    particles at any given time. Default is 500.
   * @param {Function} p Particle generator function which returns a new particle.
   * @param {CanvasRenderingContext2D=} ctx Optional. The canvas context we want to render to.
   */
  function Emitter(x, y, m, p, ctx_) {
    this.ctx = ctx || ctx_;
    this.x = x;
    this.y = y;
    this.maxParticles = m || 500;
    // Array to hold all particles we have generated for this emitter.
    this.particles = [];
    this.generateParticle = p;
    this.emit = false;
    this.images = [];
  }

  Emitter.prototype.start = function() {
    this.emit = true;
  };

  Emitter.prototype.stop = function() {
    this.emit = false;
  };
  
  Emitter.prototype.fetchCtx = function() {
    return this.ctx;
  };

  Emitter.prototype.preloadImage = function(url) {
    var img = new Image();
    var that = this;
    var id = this.images.length;
    that.images.push({"image": img, "loaded": false});
    this.attachHandler(img, 'load', function() {
      that.images[id].loaded = true;
    });
    img.crossOrigin = '';
    img.src = url;
    return id;
  };

  Emitter.prototype.attachHandler = attachHandler_;

  /**
   * Cross browser attach event handler.
   * @param {String | Object} elementId ID of the DOM element we wish to attach
   *     event to. Alternatively can pass the object itself.
   * @param {String} event The type of event to listen for eg 'click'.
   * @param {Function} functionName The function to be attached to the event.
   */
  function attachHandler_(elementId, event, functionName) {
    var element = {};
    if (typeof elementId === 'string') {
      element = document.getElementById(elementId);
    } else {
      element = elementId;
    }
    if (element.addEventListener) {
      element.addEventListener(event, functionName, false);
    } else if (element.attachEvent) {
      element.attachEvent('on' + event, functionName);
    } else {
      element['on' + event] = functionName;
    }
  }

  /**
   * Generate a random number from 0 to (n - s).
   * @param {Number} n The highest number we wish to generate numbers to.
   * @param {Number} s Optional: A number to be subtracted from the resulting
   *    random number. This allows us to generate negative numbers.
   * @param {Boolean} r If true, rounds the generated number to whole number.
   *    Else it is a double.
   */
  function randomNumber_(n, s, r) {
    if (n === undefined) {
      n = 1;
    }
    if (s === undefined) {
      s = 0;
    }
    if (r === undefined) {
      r = true;
    }
    if (r) {
      return Math.round((Math.random() * n) - s);
    } else {
      return (Math.random() * n) - s;
    }
  }

  /**
   * A function to actually draw the particle to the screen once the context
   * and other parameters have been set up.
   * @param {Particle} particle The particle we wish to draw.
   * @param {Number} width The calculated width of the particle at a given point in life.
   * @param {Number} height The calculated height of the particle at a given point in life.
   * @param {Image} image Optional. The image we wish to draw as the particle. If no image specified we draw a rectangle instead.
   */
  function renderParticle(canvasCtx, particle, width, height, image) {
    // Only rotate if rotation is applied.
    if (particle.rotation !== 0) {
      // Save context so we can revert to it later.
      canvasCtx.save();
      // Move to where we want to draw our image.
      canvasCtx.translate(particle.x, particle.y);
      // Rotate around that point.
      canvasCtx.rotate(particle.rotation);

      if (image !== undefined) {
        // Draw the image about its centre.
        canvasCtx.drawImage(image, -width / 2, -height / 2, width, height);
      } else {
        // Draw rectangle.
        canvasCtx.fillRect(-width / 2, -height / 2, width, height);
      }
      // Restore context to how it was before.
      canvasCtx.restore();
    } else {
      if (image !== undefined) {
        // Draw the image about its centre.
        canvasCtx.drawImage(image, particle.x, particle.y, width, height);
      } else {
        // Draw rectangle.
        canvasCtx.fillRect(particle.x, particle.y, width, height);
      }
    }
  }

  /**
   * Our main animation loop to update each particle's state and redraw the scene.
   * @param {Number} time The epoch time passed by the browser from the
   *    requestAnimationFrame request.
   */
  function particleLoop(time) {
    // Now lets iterate through all emitters.
    var n = emitters.length;
    // Avoid re-declaring width and height tmp vars.
    var width = 0;
    var height = 0;

    while (n--) {
      // Clear the canvas.
      emitters[n].fetchCtx().clearRect(0, 0, canvas.width, canvas.height);
      // Ensure we uphold maximum particle count.
      if (emitters[n].particles.length === emitters[n].maxParticles) {
        emitters[n].particles.shift();
      }
      // Only generate new particles if emitter is turned on.
      if (emitters[n].emit) {
        // Generate a new particle and push it to our particles array.
        var diff = emitters[n].maxParticles - emitters[n].particles.length;
        var wanted = Math.ceil(diff * 0.01);
        if (diff > 0) {
          var j = 0;
          while (j < wanted) {
            emitters[n].particles.push(emitters[n].generateParticle());
            j++;
          }
        }
      }
      // Now lets iterate through all particles and draw them all in the next time step.
      var i = emitters[n].particles.length;
      while (i--) {
        // Check to see if particle has died.
        if (emitters[n].particles[i].life > 0) {
          // Define how particle size changes with life.
          if (emitters[n].particles[i].growthType === 1) {
            // Decrease size with life of particle.
            width = (emitters[n].particles[i].life / emitters[n].particles[i].maxLife) * emitters[n].particles[i].width;
            height = (emitters[n].particles[i].life / emitters[n].particles[i].maxLife) * emitters[n].particles[i].height;
          } else if (emitters[n].particles[i].growthType === 2) {
            // Increase size with life of particle.
            width = (1 - (emitters[n].particles[i].life / emitters[n].particles[i].maxLife)) * emitters[n].particles[i].width;
            height = (1 - (emitters[n].particles[i].life / emitters[n].particles[i].maxLife)) * emitters[n].particles[i].height;
          } else {
            // No effect with life - same size throughout.
            width = emitters[n].particles[i].width;
            height = emitters[n].particles[i].height;
          }

          // If we are not trying to render an image particle then draw rectangle.
          if (emitters[n].particles[i].image === undefined) {
            emitters[n].fetchCtx().fillStyle = 'rgba(' + emitters[n].particles[i].r + ',' + emitters[n].particles[i].g + ',' + emitters[n].particles[i].b + ', 1)';
            emitters[n].fetchCtx().globalAlpha = (emitters[n].particles[i].life / emitters[n].particles[i].maxLife);
            // Render particle as rectangle.
            renderParticle(emitters[n].fetchCtx(), emitters[n].particles[i], width, height);
          } else {
            // Otherwise lets ensure image has been defined and loaded and draw it.
            if (emitters[n].images[emitters[n].particles[i].image] !== undefined) {
              if (emitters[n].images[emitters[n].particles[i].image].loaded) {
                emitters[n].fetchCtx().globalAlpha = (emitters[n].particles[i].life / emitters[n].particles[i].maxLife);
                // Render particle as image.
                renderParticle(emitters[n].fetchCtx(), emitters[n].particles[i], width, height, emitters[n].images[emitters[n].particles[i].image].image);
              }
            }
          }
          // Update particle properties for next cycle.
          emitters[n].particles[i].x += emitters[n].particles[i].xVelocity;
          emitters[n].particles[i].y += emitters[n].particles[i].yVelocity;
          emitters[n].particles[i].life--;
        } else {
          // If dead, remove it.
          emitters[n].particles.splice(i, 1);
        }
      }
    }
    requestAnimationFrame(particleLoop, canvas);
  }

  // Publicly accessible functions exposed to users.
  return {
    /**
     * Initiate and run the particle engine.
     * @param {String} canvasId The DOM id of the canvas element we shall be rendering to.
     * @param {Number} width Width of canvas.
     * @param {Number} height Height of canvas.
     */
    init: function(canvasId, width, height) {
      canvas = document.getElementById(canvasId);
      canvas.width = width;
      canvas.height = height;
      ctx = canvas.getContext('2d');
      particleLoop();
    },

    /**
     * Allow external users to generate new emitters.
     * @param {Number} x The x co-ordinate of the emitter location.
     * @param {Number} y The y co-ordinate of the emitter location.
     * @param {Number} m  Enforce a hard limit on the number of displayable
     *    particles at any given time. Default is 500.
     * @param {Function} p Particle generator function to use which returns new particles.
     * @param {CanvasRenderingContext2D=} ctx Optional. The canvas context we want to render to.
     */
    generateEmitter: function(x, y, m, p, c) {
      return new Emitter(x, y, m, p, c);
    },

    /**
     * Allow external users to generate new particles.
     * @param {Number} x Starting x co-ordinate of the particle.
     * @param {Number} y Starting y co-ordinate of the particle.
     * @param {Number} w Width of particle.
     * @param {Number} h Height of particle.
     * @param {Number} rot Rotation in radians of the particle.
     * @param {Number} xVel Velocity in the x direction.
     * @param {Number} yVel Velocity in the y direction.
     * @param {Number} life How long will the particle last for in animation frames.
     * @param {Number} s How will particle size change with life? 0 = no change,
     *    1 = smaller, 2 = larger. Taken as a % of width / height.
     * @param {Number} r The amount of red in the particle (0 - 255).
     * @param {Number} g The amount of green in the particle (0 - 255).
     * @param {Number} b The amount of blue in the particle (0 - 255).
     * @param {Number} i Optional. The index of a preloaded image we wish to
     *     render.
     */
    generateParticle: function(x, y, w, h, rot, xVel, yVel, life, s, r, g, b, i) {
      return new Particle(x, y, w, h, rot, xVel, yVel, life, s, r, g, b, i);
    },

    /**
     * Add an emitter to the engine and start it.
     * @param {jmParticleEngine.Emitter} e The emitter we wish to add to the engine.
     */
    addEmitter: function(e, s) {
      if (s !== undefined && s) {
        e.start();
      }
      emitters.push(e);
    },

    /**
     * Publicly expose the randomNumber generator.
     * @param {Number} n The highest number we wish to generate numbers to.
     * @param {Number} s Optional: A number to be subtracted from the resulting random number.
     *    This allows us to generate negative numbers.
     * @param {Boolean} r If true, rounds the generated number to whole number.
     *    Else it is a double.
     */
    randomNumber: randomNumber_,

    /**
     * Publicly expose cross browser attach event handler.
     * @param {String} elementId ID of the DOM element we wish to attach event to.
     * @param {String} event The type of event to listen for eg 'click'.
     * @param {Function} functionName The function to be attached to the event.
     */
    attachHandler: attachHandler_
  };
}();




/* Lets use this sweet engine! Example usage below! */

// Initiate engine to draw to DOM canvas with id "myCanvas" and detail its dimensions.
jmParticleEngine.init('myCanvas', window.innerWidth, window.innerHeight);

// Define a particle generators - each generates particles of one particle type.
function particleGenerator1() {
  var size = jmParticleEngine.randomNumber(2, undefined, true);
  // Note context of this is bound to the emitter calling the function,
  // so we can simply grab the emitter's x and y for its starting point.
  return jmParticleEngine.generateParticle(
      // Start at the emitter's x co-ordinate.
      this.x,
      // Start at the emitter's y co-ordinate.
      this.y,
      // Width.
      size,
      // Height.
      size,
      // Rotation.
      0,
      // xVelocity.
      jmParticleEngine.randomNumber(15, 7.5, false),
      // yVelocity.
      jmParticleEngine.randomNumber(15, 7.5, false),
      // Life.
      120,
      // How will particle change size vs life.
      // 0 - no change, same size always.
      // 1 - smaller with age.
      // 2 - larger with age.
      0,
      // Red.
      jmParticleEngine.randomNumber(191, -64, true),
      // Green.
      jmParticleEngine.randomNumber(191, -64, true),
      // Blue.
      jmParticleEngine.randomNumber(191, -64, true)
  );
}

function particleGenerator2() {
  var size = jmParticleEngine.randomNumber(20, undefined, true);
  // Note context of this is bound to the emitter calling the function,
  // so we can simply grab the emitter's x and y for its starting point.
  return jmParticleEngine.generateParticle(
      // Start at the emitter's x co-ordinate.
      this.x,
      // Start at the emitter's y co-ordinate.
      this.y,
      // Width.
      size,
      // Height.
      size,
      // Rotation.
      0,
      // xVelocity.
      jmParticleEngine.randomNumber(15, 7.5, false),
      // yVelocity.
      jmParticleEngine.randomNumber(15, 7.5, false),
      // Life.
      64,
      // How will particle change size vs life.
      // 0 - no change, same size always.
      // 1 - smaller with age.
      // 2 - larger with age.
      0,
      // Red.
      jmParticleEngine.randomNumber(255, 0, true),
      // Green.
      jmParticleEngine.randomNumber(64, 0, true),
      // Blue.
      jmParticleEngine.randomNumber(32, 0, true)
  );
}

function particleGenerator3() {
  var size1 = jmParticleEngine.randomNumber(3, undefined, true);
  var size2 = jmParticleEngine.randomNumber(100, undefined, true);
  // Note context of this is bound to the emitter calling the function,
  // so we can simply grab the emitter's x and y for its starting point.
  return jmParticleEngine.generateParticle(
      // Start at the emitter's x co-ordinate.
      this.x +  jmParticleEngine.randomNumber(1000, 500, false),
      // Start at the emitter's y co-ordinate.
      this.y,
      // Width.
      size1,
      // Height.
      size2,
      // Rotation.
      0,
      // xVelocity.
      jmParticleEngine.randomNumber(1, 1, false),
      // yVelocity.
      jmParticleEngine.randomNumber(30, 0, false),
      // Life.
      60,
      // How will particle change size vs life.
      // 0 - no change, same size always.
      // 1 - smaller with age.
      // 2 - larger with age.
      1,
      // Red.
      jmParticleEngine.randomNumber(32, 0, true),
      // Green.
      jmParticleEngine.randomNumber(50, 0, true),
      // Blue.
      jmParticleEngine.randomNumber(255, 0, true)
  );
}

function particleGenerator4() {
  var size = jmParticleEngine.randomNumber(128, undefined, true);
  var grey = jmParticleEngine.randomNumber(255, 0, true);
  // Note context of this is bound to the emitter calling the function,
  // so we can simply grab the emitter's x and y for its starting point.
  return jmParticleEngine.generateParticle(
      // Start at the emitter's x co-ordinate.
      this.x,
      // Start at the emitter's y co-ordinate.
      this.y,
      // Width.
      size,
      // Height.
      size,
      // Rotation.
      0,
      // xVelocity.
      jmParticleEngine.randomNumber(15, 7.5, false),
      // yVelocity.
      jmParticleEngine.randomNumber(15, 7.5, false),
      // Life.
      42,
      // How will particle change size vs life.
      // 0 - no change, same size always.
      // 1 - smaller with age.
      // 2 - larger with age.
      0,
      // Red.
      grey,
      // Green.
      grey,
      // Blue.
      grey
  );
}

function particleGenerator5() {
  var size = jmParticleEngine.randomNumber(128, -64, true);
  // Note context of this is bound to the emitter calling the function,
  // so we can simply grab the emitter's x and y for its starting point.
  return jmParticleEngine.generateParticle(
      // Start at the emitter's x co-ordinate.
      this.x ,
      // Start at the emitter's y co-ordinate.
      this.y ,
      // Width.
      size,
      // Height.
      size,
      // Rotation.
      jmParticleEngine.randomNumber(Math.PI * 2, 0, false),
      // xVelocity.
      jmParticleEngine.randomNumber(18, 9, false),
      // yVelocity.
      jmParticleEngine.randomNumber(18, 9, false),
      // Life.
      64,
      // How will particle change size vs life.
      // 0 - no change, same size always.
      // 1 - smaller with age.
      // 2 - larger with age.
      0,
      // Red.
      0,
      // Green.
      0,
      // Blue.
      0,
      // If we wish to use a preloaded image, specify index here.
      0
  );
}

// Generate emitters using the particle generator function defined above.
var emit1 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 1500, particleGenerator5);
emit1.preloadImage('https://storage.googleapis.com/jm-cors/images/fire2.png');

// We can also pass custom Canvas Context if we want to draw to a different canvas.
const customContext = document.getElementById('myCanvas').getContext('2d');
var emit2 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 5000, particleGenerator2, customContext);

var emit3 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 5000, particleGenerator3);

var emit4 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 5000, particleGenerator4);

var emit5 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 5000, particleGenerator1);

var emitTmp = jmParticleEngine.generateEmitter(Math.ceil((window.innerWidth / 4) * 3), Math.ceil(window.innerHeight / 2), 750, particleGenerator1);

// Add emitters to engine! Ensure emit1 and emitTmp start
// straight away.
jmParticleEngine.addEmitter(emit1, true);
jmParticleEngine.addEmitter(emit2);
jmParticleEngine.addEmitter(emit3);
jmParticleEngine.addEmitter(emit4);
jmParticleEngine.addEmitter(emit5);
jmParticleEngine.addEmitter(emitTmp, true);

// Attach emitters 1, 2, 3 and 4 to mouse position.
jmParticleEngine.attachHandler('myCanvas', 'mousemove', function(e){
  if (!e) {
    e = window.event;
  }
  emit1.x = e.clientX;
  emit1.y = e.clientY;
  emit2.x = e.clientX;
  emit2.y = e.clientY;
  emit3.x = e.clientX;
  emit3.y = e.clientY;
  emit4.x = e.clientX;
  emit4.y = e.clientY;
  emit5.x = e.clientX;
  emit5.y = e.clientY;
});

// Stop the second emitter after 5 seconds.
setTimeout(function(){
  emitTmp.stop();
}, 5000);


// Event listener for changing particle effect dropdown.
jmParticleEngine.attachHandler('changer', 'change', function(e){
  if (!e) {
    e = window.event;
  }
  var sel = document.getElementById('changer');
  var val = sel[sel.selectedIndex].value;
  emit1.stop();
  emit2.stop();
  emit3.stop();
  emit4.stop();
  emit5.stop();
  if (val === '1') {
    emit1.start();
  } else if (val === '2') {
    emit2.start();
  } else if (val === '3') {
    emit3.start();
  } else if (val === '4') {
    emit4.start();
  } else if (val === '5') {
    emit5.start();
  }
});
              
            
!
999px

Console