div.overlay Icons made by #[a(href="https://www.flaticon.com/authors/good-ware" title="Good Ware" target="_blank") Good Ware] from #[a(href="https://www.flaticon.com/" title="Flaticon" target="_blank") www.flaticon.com]

script.
	window.canvasOptions = {
		autoClear: true,
		autoCompensate: false,
		autoPushPop: true,
		canvas: true,
		//- centered: true,
		desynchronized: false,
		width: null,
		height: null
	};
View Compiled
.overlay {
	top: auto;
	left: auto;
	right: 10px;
	bottom: 10px;
	color: hsl(0, 0%, 100%);
	opacity: 0.2;
	font-family: monospace;
	
	a {
		color: hsl(190, 60%, 70%);
	}
	&:hover {
		opacity: 0.8;
	}
}
View Compiled
// Options editor: https://codepen.io/Alca/pen/MWYwxoN
/* requestAnimationFrame(); */

const SPRITE_SIZE = 32;

let flakes = [];
let spritesheet;
let SPRITE_SCALE = 1;
let MAX_FLAKES = 100;
let WIND_SCALE = 1;
let WIND_DENSITY = 1;
let GRAVITY = 1;

let GRAVITY_BASE = 1.5;
let GRAVITY_SPEED = 1.5;

const simplex = new SimplexNoise();

function setup() {
	loadImage('https://r2.alca.tv/sharex/2019/12/03/2f7ac68a-29ca-4397-8735-f4a7c226834e.png')
	.then(img => spritesheet = img);
	
	// ?sprite_scale=1.0&max_flakes=100&wind_scale=1&wind_density=1
	const _qs = new URLSearchParams(location.search);
	const qs = new URLSearchParams();
	for(const [ key, value ] of _qs.entries()) {
		qs.append(key.toLowerCase(), value);
	}
	if(qs.has('sprite_scale')) {
		const _val = qs.get('sprite_scale');
		if(_val) {
			const val = parseFloat(_val, 10);
			if(!isNaN(val) && val > 0) {
				SPRITE_SCALE = val;
			}
		}
	}
	if(qs.has('max_flakes')) {
		const _val = qs.get('max_flakes');
		if(_val) {
			const val = parseFloat(_val, 10);
			if(!isNaN(val) && val >= 0) {
				MAX_FLAKES = val;
			}
		}
	}
	if(qs.has('gravity')) {
		const _val = qs.get('gravity');
		if(_val) {
			const val = parseFloat(_val, 10);
			if(!isNaN(val)) {
				GRAVITY = val;
			}
		}
	}
	GRAVITY_BASE *= GRAVITY;
	GRAVITY_SPEED *= GRAVITY;
	if(qs.has('wind_scale')) {
		const _val = qs.get('wind_scale');
		if(_val) {
			const val = parseFloat(_val, 10);
			if(!isNaN(val)) {
				WIND_SCALE = val;
			}
		}
	}
	if(qs.has('wind_density')) {
		const _val = qs.get('wind_density');
		if(_val) {
			const val = parseFloat(_val, 10);
			if(!isNaN(val)) {
				WIND_DENSITY = val;
			}
		}
	}
	if(qs.has('hide_overlay')) {
		const ele = document.querySelector('.overlay');
		ele.remove();
	}
	
	for(let i = 0; i < MAX_FLAKES; i++) {
		const f = new Snowflake();
		f._pos.setY(
			// random(-SPRITE_SIZE, width),
			random(-SPRITE_SIZE, height)
		);
		flakes.push(f);
	}
}

function draw(e) {
	if(!spritesheet) {
		return;
	}
	// if(flakes.length < MAX_FLAKES) {
	// 	const f = new Snowflake();
	// 	f._pos.set(random(-SPRITE_SIZE, width), -SPRITE_SIZE);
	// 	flakes.push(f);
	// }
	const time = e * 0.001;
	const windTime = time * 0.1 * WIND_DENSITY;
	beginPath();
	for(let i = flakes.length - 1; i >= 0; i--) {
		const f = flakes[i];
		f.update(time, windTime);
		f.draw(time);
		if(
			(GRAVITY >= 0 && f.pos.y > height + SPRITE_SIZE) ||
			(GRAVITY < 0 && f.pos.y < - SPRITE_SIZE)
		) {
			// flakes.splice(i, 1);
			f.randomize();
		}
	}
	stroke(hsl(0, 0, 100));
}

const sclRngs = [
	[ 0.12, 0.2 ], [ 0.25, 0.4 ], [ 0.5, 0.7 ], [ 0.8, 1 ]
];

const scales = [
	sclRngs[0], sclRngs[0], sclRngs[0],
	sclRngs[1], sclRngs[1], sclRngs[1], sclRngs[1],
	sclRngs[2], sclRngs[2], sclRngs[2], sclRngs[2],
	sclRngs[3], sclRngs[3]
];

class Snowflake {
	_pos = new Vector();
	pos = new Vector();
	// scale; img; offset; rot;
	constructor() {
		this.randomize();
	}
	randomize() {
		this._pos.set(random(-SPRITE_SIZE, width), -SPRITE_SIZE);
		this.scale = SPRITE_SCALE * random(...random(scales));
		this.img = [ random([ 0, 1, 2, 3 ]), random([ 0, 1, 2, 3 ]) ];
		this.offset = random(10000000);
		this.rot = random(0.1, 1) * PI * random([ -1, 1 ]);
	}
	update(e = performance.now(), windTime) {
		const { _pos, pos, scale, offset } = this;
		let wind = 0;
		if(WIND_SCALE) {
			wind = WIND_SCALE * simplex.noise3D(
				..._pos._.mult(WIND_DENSITY * 0.0002 * (1 - scale * 0.5)).xy,
				windTime
			);
		}
		_pos.add(
			wind,
			ease.expo.inOut(scale, GRAVITY_BASE, GRAVITY_SPEED, 1)
		);
		pos.set(_pos)
		.add(
			sin(offset + e * scale) * (scale + 1) * 10,
			0
		);
	}
	draw(e = performance.now()) {
		const { pos, vel, scale: flakeScale, img, offset, rot } = this;
		const size = SPRITE_SIZE * flakeScale;
		push();
		translate(pos);
		rotate(offset * flakeScale + rot * e);
		const r = 0;
		rotate(r);
		scale(sin((e + offset) * rot), 1);
		rotate(-r);
		drawImage(
			spritesheet,
			img[0] * SPRITE_SIZE, img[1] * SPRITE_SIZE,
			SPRITE_SIZE, SPRITE_SIZE,
			-size * 0.5, -size * 0.5,
			size, size
		);
		pop();
	}
}
View Compiled

External CSS

  1. https://codepen.io/Alca/pen/XeZBab.scss

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js
  2. https://codepen.io/Alca/pen/XeZBab.js