How can I add snow particles to my web game?

I want to have snow particles for the winter season, and possibly a little particle effect when you purchase an upgrade.
https://replit.com/@BluebayStudios/Ples-nerd

Did this a few years ago using TSParticles. It does it in December and January on my website. You can check out the code for the homepage by pasting this into your browser: view-source:https://nltesting.netlify.app (specifically, around line 278)

You can follow their docs: https://particles.js.org/docs/

Also, if you set up TSParticles right, then you can just use my configuration below:

{
  "autoPlay": true,
  "background": {
    "color": { "value": "transparent" },
    "image": "",
    "position": "50% 50%",
    "repeat": "no-repeat",
    "size": "cover",
    "opacity": 1
  },
  "backgroundMask": {
    "composite": "destination-out",
    "cover": { "color": { "value": "#fff" }, "opacity": 1 },
    "enable": false
  },
  "fullScreen": { "enable": true, "zIndex": 0 },
  "detectRetina": true,
  "duration": 0,
  "fpsLimit": 60,
  "interactivity": {
    "detectsOn": "window",
    "events": {
      "resize": true
    }
  },
  "motion": { "disable": false, "reduce": { "factor": 4, "value": true } },
  "particles": {
    "bounce": {
      "horizontal": {
        "random": { "enable": false, "minimumValue": 0.1 },
        "value": 1
      },
      "vertical": {
        "random": { "enable": false, "minimumValue": 0.1 },
        "value": 1
      }
    },
    "collisions": {
      "bounce": {
        "horizontal": {
          "random": { "enable": false, "minimumValue": 0.1 },
          "value": 1
        },
        "vertical": {
          "random": { "enable": false, "minimumValue": 0.1 },
          "value": 1
        }
      },
      "enable": false,
      "mode": "bounce",
      "overlap": { "enable": true, "retries": 0 }
    },
    "color": {
      "value": "#fff",
      "animation": {
        "h": {
          "count": 0,
          "enable": false,
          "offset": 0,
          "speed": 1,
          "sync": true
        },
        "s": {
          "count": 0,
          "enable": false,
          "offset": 0,
          "speed": 1,
          "sync": true
        },
        "l": {
          "count": 0,
          "enable": false,
          "offset": 0,
          "speed": 1,
          "sync": true
        }
      }
    },
    "destroy": {
      "mode": "none",
      "split": {
        "count": 1,
        "factor": {
          "random": { "enable": false, "minimumValue": 0 },
          "value": 3
        },
        "rate": {
          "random": { "enable": false, "minimumValue": 0 },
          "value": { "min": 4, "max": 9 }
        },
        "sizeOffset": true
      }
    },
    "life": {
      "count": 0,
      "delay": {
        "random": { "enable": false, "minimumValue": 0 },
        "value": 0,
        "sync": false
      },
      "duration": {
        "random": { "enable": false, "minimumValue": 0.0001 },
        "value": 0,
        "sync": false
      }
    },
    "links": {
      "blink": false,
      "color": { "value": "#ffffff" },
      "consent": false,
      "distance": 500,
      "enable": false,
      "frequency": 1,
      "opacity": 1,
      "shadow": { "blur": 5, "color": { "value": "#00ff00" }, "enable": false },
      "triangles": { "enable": false, "frequency": 1 },
      "width": 2,
      "warp": false
    },
    "move": {
      "angle": { "offset": 0, "value": 90 },
      "attract": {
        "distance": 200,
        "enable": false,
        "rotate": { "x": 600, "y": 1200 }
      },
      "decay": 0,
      "distance": {},
      "direction": "bottom",
      "drift": 0,
      "enable": true,
      "gravity": {
        "acceleration": 9.81,
        "enable": false,
        "inverse": false,
        "maxSpeed": 50
      },
      "path": {
        "clamp": true,
        "delay": {
          "random": { "enable": false, "minimumValue": 0 },
          "value": 0
        },
        "enable": false,
        "options": {}
      },
      "outModes": {
        "default": "out",
        "bottom": "out",
        "left": "out",
        "right": "out",
        "top": "out"
      },
      "random": false,
      "size": false,
      "speed": 2,
      "spin": { "acceleration": 0, "enable": false },
      "straight": false,
      "trail": {
        "enable": false,
        "length": 10,
        "fillColor": { "value": "#000000" }
      },
      "vibrate": false,
      "warp": false
    },
    "number": {
      "density": { "enable": true, "area": 800, "factor": 1000 },
      "limit": 0,
      "value": 400
    },
    "opacity": {
      "value": { "min": 1, "max": 1 },
      "animation": {
        "count": 0,
        "enable": false,
        "speed": 1,
        "sync": false,
        "destroy": "none",
        "startValue": "random",
        "minimumValue": 0.1
      }
    },
    "reduceDuplicates": false,
    "repulse": {
      "random": { "enable": false, "minimumValue": 0 },
      "value": 0,
      "enabled": false,
      "distance": 1,
      "duration": 1,
      "factor": 1,
      "speed": 1
    },
    "roll": {
      "darken": { "enable": false, "value": 0 },
      "enable": false,
      "enlighten": { "enable": false, "value": 0 },
      "mode": "vertical",
      "speed": 25
    },
    "rotate": {
      "random": { "enable": false, "minimumValue": 0 },
      "value": 0,
      "animation": { "enable": false, "speed": 0, "sync": false },
      "direction": "clockwise",
      "path": false
    },
    "shape": { "type": "circle" },
    "size": {
      "random": { "enable": true, "minimumValue": 4 },
      "value": { "min": 4, "max": 4 },
      "animation": {
        "count": 0,
        "enable": false,
        "speed": 40,
        "sync": false,
        "destroy": "none",
        "startValue": "random",
        "minimumValue": 0.1
      }
    }
  },
  "zLayers": 1
}
3 Likes

Can I have a bit more help setting up TSParticles?

1 Like

Make a div like this, and place it at the very top of your body:

<div style="z-index: -1;" id="tsparticles"></div>

The z-index as -1 is because that way, it will put the particles all behind your website contents instead of over it.

Next, import TSParticles (I’m using jsdelivr):

<script src="https://cdn.jsdelivr.net/npm/tsparticles@1.37.5/tsparticles.min.js"></script>

Lastly, add a script with this as the contents:

tsParticles.load("tsparticles", {
  autoPlay: true,
  background: {
    color: { value: "transparent" },
    image: "",
    position: "50% 50%",
    repeat: "no-repeat",
    size: "cover",
    opacity: 1,
  },
  backgroundMask: {
    composite: "destination-out",
    cover: { color: { value: "#fff" }, opacity: 1 },
    enable: false,
  },
  fullScreen: { enable: true, zIndex: 0 },
  detectRetina: true,
  duration: 0,
  fpsLimit: 60,
  interactivity: {
    detectsOn: "window",
    events: {
      resize: true,
    },
  },
  motion: { disable: false, reduce: { factor: 4, value: true } },
  particles: {
    bounce: {
      horizontal: { random: { enable: false, minimumValue: 0.1 }, value: 1 },
      vertical: { random: { enable: false, minimumValue: 0.1 }, value: 1 },
    },
    collisions: {
      bounce: {
        horizontal: { random: { enable: false, minimumValue: 0.1 }, value: 1 },
        vertical: { random: { enable: false, minimumValue: 0.1 }, value: 1 },
      },
      enable: false,
      mode: "bounce",
      overlap: { enable: true, retries: 0 },
    },
    color: {
      value: "#fff",
      animation: {
        h: { count: 0, enable: false, offset: 0, speed: 1, sync: true },
        s: { count: 0, enable: false, offset: 0, speed: 1, sync: true },
        l: { count: 0, enable: false, offset: 0, speed: 1, sync: true },
      },
    },
    destroy: {
      mode: "none",
      split: {
        count: 1,
        factor: { random: { enable: false, minimumValue: 0 }, value: 3 },
        rate: {
          random: { enable: false, minimumValue: 0 },
          value: { min: 4, max: 9 },
        },
        sizeOffset: true,
      },
    },
    life: {
      count: 0,
      delay: {
        random: { enable: false, minimumValue: 0 },
        value: 0,
        sync: false,
      },
      duration: {
        random: { enable: false, minimumValue: 0.0001 },
        value: 0,
        sync: false,
      },
    },
    links: {
      blink: false,
      color: { value: "#ffffff" },
      consent: false,
      distance: 500,
      enable: false,
      frequency: 1,
      opacity: 1,
      shadow: { blur: 5, color: { value: "#00ff00" }, enable: false },
      triangles: { enable: false, frequency: 1 },
      width: 2,
      warp: false,
    },
    move: {
      angle: { offset: 0, value: 90 },
      attract: { distance: 200, enable: false, rotate: { x: 600, y: 1200 } },
      decay: 0,
      distance: {},
      direction: "bottom",
      drift: 0,
      enable: true,
      gravity: {
        acceleration: 9.81,
        enable: false,
        inverse: false,
        maxSpeed: 50,
      },
      path: {
        clamp: true,
        delay: { random: { enable: false, minimumValue: 0 }, value: 0 },
        enable: false,
        options: {},
      },
      outModes: {
        default: "out",
        bottom: "out",
        left: "out",
        right: "out",
        top: "out",
      },
      random: false,
      size: false,
      speed: 2,
      spin: { acceleration: 0, enable: false },
      straight: false,
      trail: { enable: false, length: 10, fillColor: { value: "#000000" } },
      vibrate: false,
      warp: false,
    },
    number: {
      density: { enable: true, area: 800, factor: 1000 },
      limit: 0,
      value: 400,
    },
    opacity: {
      value: { min: 1, max: 1 },
      animation: {
        count: 0,
        enable: false,
        speed: 1,
        sync: false,
        destroy: "none",
        startValue: "random",
        minimumValue: 0.1,
      },
    },
    reduceDuplicates: false,
    repulse: {
      random: { enable: false, minimumValue: 0 },
      value: 0,
      enabled: false,
      distance: 1,
      duration: 1,
      factor: 1,
      speed: 1,
    },
    roll: {
      darken: { enable: false, value: 0 },
      enable: false,
      enlighten: { enable: false, value: 0 },
      mode: "vertical",
      speed: 25,
    },
    rotate: {
      random: { enable: false, minimumValue: 0 },
      value: 0,
      animation: { enable: false, speed: 0, sync: false },
      direction: "clockwise",
      path: false,
    },
    shape: { type: "circle" },
    size: {
      random: { enable: true, minimumValue: 4 },
      value: { min: 4, max: 4 },
      animation: {
        count: 0,
        enable: false,
        speed: 40,
        sync: false,
        destroy: "none",
        startValue: "random",
        minimumValue: 0.1,
      },
    },
  },
  zLayers: 1,
});

That’s it. That’s literally all.

It makes sense if you know what it all means. Otherwise, you can just paste it and never look at it again :wink:

Important: Make sure your background/page contents are dark enough that you’re able to still see the white snow. Good luck, and you can look up the API reference for TSParticles if you feel like tweaking it a little bit.

4 Likes

Thanks! This helped out a ton! I love this feature! One question though, does this only snow in december and january?

1 Like

I mean, create a JS file and link it to your HTML using a script tag.

No, that was just my implementation.

1 Like

You know, particles are really nice and simple, so for anyone interested I’ll give you a very short simple tutorial on basic particles with JS.

First of all, you need somewhere to display the particles, there are two main ways you could go about this, using DOM elements or using a HTML5 canvas. The latter is probably the best choice and is very easy to quickly implement.

Set up a canvas in your HTML page:

<canvas id="particles"></canvas>

Then obtain it in your JavaScript:

const canvas = document.getElementById("particles");

(You don’t have to do this in this exact way.)

Now there are a couple of different ways we can interact with the canvas (which you may want to consider if you are a more advanced programmer and are doing more intensive graphics with lots of particles), but the simplest and easiest is using a 2D rendering context:

const ctx = canvas.getContext("2d");

Now we can draw on the canvas.

If we want to draw particles, we will want a structure to describe them. We can create a simple class for this:

class Particle {
	// some basic information about our particle
	constructor(x, y, size, colour) {
		// where the particle is
		this.x = x;
		this.y = y;
		// its size
		this.size = size;
		// and its colour
		this.colour = colour;
	}
	// we also want to be able to update and draw our particle
	update() {
		// to be filled in later
	}
	// draw the particle using the canvas context
	draw() {
		// start drawing
		ctx.beginPath();
		// draw a circle (a complete arc)
		// the center is at x, y and its radius is size
		ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
		// colour the particle
		ctx.fillStyle = this.colour; // (this can be any valid CSS colour, but will be converted to hex automatically)
		ctx.fill(); // actually fill in the arc using the fillStyle
		// stop drawing
		ctx.closePath();
	}
}

Cool, that’s almost everything you need to describe a basic particle. There is one basic thing we’re still missing, however; particles move, so we will almost always need a way to describe/define their motion. We could go about this a few different ways: a direction and speed, or simply a velocity, let’s go with the former, it’s straightforward and easy to understand.

class Particle {
	constructor(x, y, direction, speed, size, colour) {
	// ...
	this.direction = direction;
	this.speed = speed;
	// ...

Now to make our particle move in this direction, we want to change the x and y coordinates based on direction and speed in the update method. (Time for some trigonometry.)

	// ...
	update() {
		// we're assuming our direction will be in degrees, but the built-in `sin` and `cos` expect radians
		const radians = this.direction * Math.PI / 180;
		this.x += Math.cos(radians) * speed;
		this.y += Math.sin(radians) * speed;
	}
	// ...

This is pretty much all you’ll need for a snow effect. Now you just need to create the particles and animate them. To do this, we using requestAnimationFrame:

// randomly create some particles:
const PARTICLE_COUNT = 250;
const particles = [];
for (let i = 0; i < PARTICLE_COUNT; i++) {
	const particle = new Particle(
		Math.random() * canvas.width, // set x to anywhere in the canvas
		Math.random() * canvas.height, // set y to anywhere in the canvas
		Math.random() * 50 - 25, // between -25 and 25 (since 0 degrees is the positive y direction, and in a HTML5 canvas the positive y direction is down)
		Math.random() * 5 + 2, //  between 7 and 2
		"white" // in hex: #ffffff
	);
}

function AnimateParticles() {
	for (const particle of particles) {
		// now really we should add some extra steps to ensure framerate does not affect this, but this should be fine to get a simple animation up and running
		particle.update();
		particle.draw();
	}

	window.requestAnimationFrame(AnimateParticles);
}
window.requestAnimationFrame(AnimateParticles):

Okay, but we forgot one thing, what happens when a particle goes off screen? Right now, nothing, it just keeps going infinitely, we want to move it somewhere to the top when this happens:

	// ...
	update() {
		// ...
		// if the particle is offscreen, move it back onscreen
		if (this.y + size > canvas.height) {
			this.y = -size; // move to top
			this.x = Math.random() * canvas.width; // randomise x position
			// you could also change the speed and direction
		}
	}
	// ...

And voila, you have your own basic particle system!

2 Likes

Is there a way to add a little particle effect whenever you buy an upgrade?

What do you mean “effect”? Like change color?

I want a few little particles to spew out of the button, sort of like the run button on replit (with the confetti.)

1 Like

On the tsparticles.org website, they have a confetti configuration, so you can just change it to snow or whatever particle. And to make it shoot from the button, you can probably just shrink the tsparticles div down to size and position of the button.

2 Likes

I found this tutorial, but i’m a bit confused on setting it up.

Click the direction you like best and click Toggle Code. Then copy that and apply it to a different div (just make another div with a different ID, and when you load tsparticles again, just provide that new ID.)

Thank you for your help!!!

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.