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

Auto Save

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></canvas>

<script id="default_settings" type="application/json">
{
  "preset": "rain drops",
  "closed": false,
  "remembered": {
    "rain drops": {
      "0": {
        "runAnimation": true,
        "emitterPosition_x": 0,
        "emitterPosition_y": 0
      },
      "1": {
        "min_lifetime": 3,
        "lifetime_range": 0.5,
        "min_angle": 270,
        "angle_range": 45,
        "min_speed": 30,
        "speed_range": 30,
        "min_size": 1,
        "size_range": 4
      },
      "2": {
        "color": {
          "r": 130,
          "g": 196,
          "b": 245
        },
        "alpha": 0.8
      },
      "3": {
        "color": {
          "r": 69,
          "g": 152,
          "b": 212
        },
        "alpha": 0.8
      },
      "4": {
        "color": {
          "r": 130,
          "g": 196,
          "b": 245
        },
        "alpha": 0.8
      },
      "5": {
        "color": {
          "r": 130,
          "g": 196,
          "b": 245
        },
        "alpha": 0.3
      },
      "6": {
        "color": {
          "r": 69,
          "g": 152,
          "b": 212
        },
        "alpha": 0.3
      },
      "7": {
        "color": {
          "r": 130,
          "g": 196,
          "b": 245
        },
        "alpha": 0.3
      },
      "8": {
        "emission_rate": 1000,
        "gravity_x": 20,
        "gravity_y": 100,
        "min_position_x": 100,
        "min_position_y": 0,
        "position_range_x": 1000,
        "position_range_y": 0
      }
    }
  },
  "folders": {
    "Global Settings": {
      "preset": "rain drops",
      "closed": false,
      "folders": {}
    },
    "Particle Settings": {
      "preset": "rain drops",
      "closed": false,
      "folders": {
        "Particle Start Colors": {
          "preset": "Default",
          "closed": false,
          "folders": {}
        },
        "Particle End Colors": {
          "preset": "Default",
          "closed": false,
          "folders": {}
        }
      }
    },
    "Emitter Settings": {
      "preset": "rain drops",
      "closed": false,
      "folders": {}
    }
  }
}
</script>

              
            
!

CSS

              
                body {
    margin: 0;
    overflow: hidden;
}

canvas {
    width: 100vw;
    height: 100vh;
}

              
            
!

JS

              
                var canvas = document.querySelector("canvas");

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

var context = canvas.getContext("2d");

class globalSettings {
    constructor() {
        this.runAnimation = true;
        
        this.emitterPosition_x = canvas.width / 2;
        this.emitterPosition_y = canvas.height / 2;
    }
}

class particleSettings { 
    constructor() {
        this.min_lifetime = 1;
        this.lifetime_range = 0.5;

        this.min_angle = 80;
        this.angle_range = 20;

        this.min_speed = 10;
        this.speed_range = 140;

        this.min_size = 2;
        this.size_range = 10;

        this.start_colors = {
            "Color 1": { color: {r: 239, g: 127, b: 67}, alpha: 0.7 },
            "Color 2": { color: {r: 253, g: 69, b: 69}, alpha: 0.8 },
            "Color 3": { color: {r: 239, g: 100, b: 67}, alpha: 0.7 }
        }
        
        this.end_colors = {
            "Color 1": { color: {r: 90, g: 90, b: 90}, alpha: 0 },
            "Color 2": { color: {r: 110, g: 110, b: 110}, alpha: 0 },
            "Color 3": { color: {r: 130, g: 130, b: 130}, alpha: 0 }
        }
    }
}

class emitterSettings {
    constructor() {
        this.emission_rate = 1000;

        this.gravity_x = 0;
        this.gravity_y = -100;

        this.min_position_x = -20;
        this.min_position_y = -20;

        this.position_range_x = 40;
        this.position_range_y = 40;
    }
}

class Settings {
    constructor() {
        this.global = new globalSettings();
        this.particle = new particleSettings();
        this.emitter = new emitterSettings();
    }
}

var currentSettings = new Settings();
var defaultSettings = JSON.parse(document.getElementById("default_settings").innerHTML);

var gui = new dat.GUI({
    load: defaultSettings
});

gui.width = 400;

gui.remember(currentSettings.global);
gui.remember(currentSettings.particle);

Object.keys(currentSettings.particle.start_colors).forEach((key) => {
    gui.remember(currentSettings.particle.start_colors[key]);
});

Object.keys(currentSettings.particle.end_colors).forEach((key) => {
    gui.remember(currentSettings.particle.end_colors[key]);
});

gui.remember(currentSettings.emitter);

var gui_global = gui.addFolder("Global Settings");

var gui_global_runAnim = gui_global.add(currentSettings.global, "runAnimation");
gui_global_runAnim.name("Run animation");
gui_global_runAnim.onChange( () => {
    if(gui_global_runAnim.getValue() === true) { // getValue() returns the value _after_ the change
        emitter.last_update = 0;
        emitter.last_emission = 0;
        emitter.particles = [];
        emitter.updateProperties();
        
        requestAnimationFrame(mainLoop);
    }
});

var gui_global_emitter_posX = gui_global.add(currentSettings.global, "emitterPosition_x", 0.0, canvas.width, 1.0);
gui_global_emitter_posX.name("Emitter center position X");
gui_global_emitter_posX.onChange( () => {
    emitter.updateProperties();
});

var gui_global_emitter_posY = gui_global.add(currentSettings.global, "emitterPosition_y", 0.0, canvas.height, 1.0);
gui_global_emitter_posY.name("Emitter center position Y");
gui_global_emitter_posY.onChange( () => {
    emitter.updateProperties();
});

//gui_global.open();

var gui_particles = gui.addFolder("Particle Settings");

gui_particles.add(currentSettings.particle, "min_lifetime", 0.1, 5.0, 0.1).name("min. Lifetime (in sec.)");
gui_particles.add(currentSettings.particle, "lifetime_range", 0.0, 5.0, 0.1).name("Lifetime range (in sec.)");

gui_particles.add(currentSettings.particle, "min_angle", 0.0, 360.0, 1.0).name("min. Angle (in deg.)");
gui_particles.add(currentSettings.particle, "angle_range", 0.0, 360.0, 1.0).name("Angle range (in deg.)");

gui_particles.add(currentSettings.particle, "min_speed", 0.0, 200.0, 1.0).name("min. Speed");
gui_particles.add(currentSettings.particle, "speed_range", 0.0, 200.0, 1.0).name("Speed range");

gui_particles.add(currentSettings.particle, "min_size", 0.0, 20.0, 1.0).name("min. Size");
gui_particles.add(currentSettings.particle, "size_range", 0.0, 20.0, 1.0).name("Size range");

var gui_particles_start_colors = gui_particles.addFolder("Particle Start Colors");

Object.keys(currentSettings.particle.start_colors).forEach((key) => {
    gui_particles_start_colors.addColor(currentSettings.particle.start_colors[key], "color").name(key + " RGB");
    gui_particles_start_colors.add(currentSettings.particle.start_colors[key], "alpha", 0.0, 1.0).name(key + " Alpha");
});

//gui_particles_start_colors.open();

var gui_particles_end_colors = gui_particles.addFolder("Particle End Colors");

Object.keys(currentSettings.particle.end_colors).forEach((key) => {
    gui_particles_end_colors.addColor(currentSettings.particle.end_colors[key], "color").name(key + " RGB");
    gui_particles_end_colors.add(currentSettings.particle.end_colors[key], "alpha", 0.0, 1.0).name(key + " Alpha");
});

//gui_particles_end_colors.open();

//gui_particles.open();

var gui_emitter = gui.addFolder("Emitter Settings");

gui_emitter.add(currentSettings.emitter, "emission_rate", 0.0, 5000.0, 1.0).name("Emission rate (1 / sec.)").onChange( () => { emitter.updateProperties(); });

gui_emitter.add(currentSettings.emitter, "gravity_x", -1000.0, 1000.0, 1.0).name("Gravity X").onChange( () => { emitter.updateProperties(); });
gui_emitter.add(currentSettings.emitter, "gravity_y", -1000.0, 1000.0, 1.0).name("Gravity Y").onChange( () => { emitter.updateProperties(); });

gui_emitter.add(currentSettings.emitter, "min_position_x", -500.0, 500.0, 1.0).name("min. Position X").onChange( () => { emitter.updateProperties(); });
gui_emitter.add(currentSettings.emitter, "min_position_y", -500.0, 500.0, 1.0).name("min. Position Y").onChange( () => { emitter.updateProperties(); });

gui_emitter.add(currentSettings.emitter, "position_range_x", 0.0, 1000.0, 1.0).name("Position Range X").onChange( () => { emitter.updateProperties(); });
gui_emitter.add(currentSettings.emitter, "position_range_y", 0.0, 1000.0, 1.0).name("Position Range Y").onChange( () => { emitter.updateProperties(); });

//gui_emitter.open();

gui.close();

addEventListener("resize", () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    
    gui_global_emitter_posX.max(canvas.width);
    gui_global_emitter_posY.max(canvas.height)
    
    if(gui_global_emitter_posX.getValue() > canvas.width) {
        gui_global_emitter_posX.setValue(canvas.width / 2);
    }
    
    if(gui_global_emitter_posY.getValue() > canvas.height) {
        gui_global_emitter_posY.setValue(canvas.height / 2);
    }
    
    gui_global_emitter_posX.updateDisplay();
    gui_global_emitter_posY.updateDisplay();
    
    emitter.updateProperties();
});

class Particle {
    constructor(x,y) {
        this.min_size = currentSettings.particle.min_size;
        this.size_range = currentSettings.particle.size_range;
        
        this.min_angle = currentSettings.particle.min_angle;
        this.angle_range = currentSettings.particle.angle_range;
        
        this.min_speed = currentSettings.particle.min_speed;
        this.speed_range = currentSettings.particle.speed_range;
        
        this.min_lifetime = currentSettings.particle.min_lifetime;
        this.lifetime_range = currentSettings.particle.lifetime_range;
        
        this.start_colors = currentSettings.particle.start_colors;
        this.end_colors = currentSettings.particle.end_colors;
        
        // the particle's position
        this.position_x = x;
        this.position_y = y;
        
        // state of the particle's life
        this.time_lived = 0;
        this.is_dead = false;

        // calculate the particle's properties based on the settings
        this.size = this.min_size + Math.random() * this.size_range;

        this.angle = this.min_angle + Math.random() * this.angle_range;
        this.speed = this.min_speed + Math.random() * this.speed_range;

        this.lifetime = this.min_lifetime + Math.random() * this.lifetime_range;
        
        // the particle's velocity
        this.velocity_x = Math.cos(this.angle * Math.PI / 180) * this.speed;
        this.velocity_y = -Math.sin(this.angle * Math.PI / 180) * this.speed;

        // the particle's color values
        this.start_color = Object.values(this.start_colors)[Math.floor(Object.values(this.start_colors).length * Math.random())];
        this.end_color = Object.values(this.end_colors)[Math.floor(Object.values(this.end_colors).length * Math.random())];

        this.color = {
            r: this.start_color.color.r, // red
            g: this.start_color.color.g, // green
            b: this.start_color.color.b, // blue
            a: this.start_color.alpha    // alpha
        };
        
        this.color_step = {
            r: (this.end_color.color.r - this.start_color.color.r) / this.lifetime, // red
            g: (this.end_color.color.g - this.start_color.color.g) / this.lifetime, // green
            b: (this.end_color.color.b - this.start_color.color.b) / this.lifetime, // blue
            a: (this.end_color.alpha   - this.start_color.alpha  ) / this.lifetime // alpha
        };
    }
    
    update(seconds_since_last_update) {
        // calculate the particle's new position based on the forces multiplied by seconds passed
        this.velocity_x += emitter.gravity_x * seconds_since_last_update;
        this.velocity_y += emitter.gravity_y * seconds_since_last_update;

        this.position_x += this.velocity_x * seconds_since_last_update;
        this.position_y += this.velocity_y * seconds_since_last_update;

        // calculate new color and draw the particle
        this.color.r += this.color_step.r * seconds_since_last_update;
        this.color.g += this.color_step.g * seconds_since_last_update;
        this.color.b += this.color_step.b * seconds_since_last_update;
        this.color.a += this.color_step.a * seconds_since_last_update;
    }
    
    render() {
        var r = Math.round(this.color.r);
        var g = Math.round(this.color.g);
        var b = Math.round(this.color.b);
        var a = this.color.a;
        
        context.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`;

        context.beginPath();
        context.arc(this.position_x, this.position_y, this.size, 0, Math.PI * 2);
        context.fill();
    }
}

class Emitter {
    constructor() {
        // the emitter's position
        this.pos_x = 0;
        this.pos_y = 0;

        this.gravity_x = 0;
        this.gravity_y = 0;
        
        // How often the emitter needs to create a particle per second
        this.emission_rate = 1;
        
        // How long to wait, before a new particle is created (in milliseconds)
        this.emission_delay = 1000 / this.emission_rate;

        // particle position settings
        this.min_position_x = 0;
        this.min_position_y = 0;

        this.position_range_x = 0;
        this.position_range_y = 0;
        
        this.last_update = 0;
        this.last_emission = 0;

        // the emitter's particle objects
        this.particles = [];
    }
    
    reset() {
        this.last_update = 0;
        this.last_emission = 0;

        // the emitter's particle objects
        this.particles = [];

        this.updateProperties();        
    }
    
    updateProperties() {
        // the emitter's position
        this.pos_x = currentSettings.global.emitterPosition_x;
        this.pos_y = currentSettings.global.emitterPosition_y;

        this.gravity_x = currentSettings.emitter.gravity_x;
        this.gravity_y = currentSettings.emitter.gravity_y;
        
        // How often the emitter needs to create a particle per second
        this.emission_rate = currentSettings.emitter.emission_rate;
        
        // How long to wait, before a new particle is created (in milliseconds)
        this.emission_delay = 1000 / this.emission_rate;

        // particle position settings
        this.min_position_x = currentSettings.emitter.min_position_x;
        this.min_position_y = currentSettings.emitter.min_position_y;

        this.position_range_x = currentSettings.emitter.position_range_x;
        this.position_range_y = currentSettings.emitter.position_range_y;
    }
    
    render() {
        // set the last_update variable to now if it's the first update
        if (this.last_update === 0) {
            this.last_update = Date.now();
            return;
        }

        context.save();
        context.translate(this.pos_x, this.pos_y);
            
        // get the current time
        var time = Date.now();

        // work out the milliseconds since the last update
        var ms_since_last_update = time - this.last_update;

        // add them to the milliseconds since the last particle emission
        this.last_emission += ms_since_last_update;

        // set last_update to now
        this.last_update = time;

        // check if we need to emit a new particle
        if (this.last_emission > this.emission_delay) {

            // find out how many particles we need to emit
            var particle_count_to_emit = Math.floor(this.last_emission / this.emission_delay);

            // subtract the appropriate amount of milliseconds from last_emission
            this.last_emission -= particle_count_to_emit * this.emission_delay;

            while (particle_count_to_emit--) {
                var particle_position_x = this.min_position_x + Math.random() * this.position_range_x;
                var particle_position_y = this.min_position_y + Math.random() * this.position_range_y;
                
                this.particles.push(new Particle(particle_position_x, particle_position_y));
            }
        }

        // convert milliseconds to seconds
        var seconds_since_last_update = ms_since_last_update / 1000;

        // loop through the existing particles
        var particle_number_to_process = this.particles.length;

        while (particle_number_to_process--) {
            var particle = this.particles[particle_number_to_process];

            // skip if the particle is dead
            if (particle.is_dead) {

                // remove the particle from the array
                this.particles.splice(particle_number_to_process, 1);
                continue;
            }

            // add the seconds passed to the particle's lifetime
            particle.time_lived += seconds_since_last_update;

            // check if the particle should be dead
            if (particle.time_lived >= particle.lifetime) {
                particle.is_dead = true;
                continue;
            }

            // calculate the particle's new position based on the forces multiplied by seconds passed
            particle.update(seconds_since_last_update);

            // draw the particle
            particle.render();
        }

        context.restore();
    }
}

var emitter = new Emitter();
emitter.updateProperties();

function mainLoop() {
    if (currentSettings.global.runAnimation) {
        requestAnimationFrame(mainLoop);
    }
    
    context.clearRect(0, 0, canvas.width, canvas.height);
    
    emitter.render();
}

requestAnimationFrame(mainLoop);

              
            
!
999px

Console