A browser source overlay for winter vibes for your Live Streams or Videos
1import "./style.css"; 2import Config from "./config"; 3import { createSnowflake, drawSnowflake, moveSnowflake } from "./snowflake"; 4import { 5 accumulateSnow, 6 snowAccumulatorCollisionY, 7 createSnowAccumulator, 8 drawSnowAccumulators, 9 resetSnowAccumulator, 10 snowAccumulatorCollisionX, 11} from "./snow_accumulator"; 12import { createPlow, drawPlow, movePlow, plowDone } from "./plow"; 13 14/** 15 * @type{Array.<Snowflake>} 16 */ 17const snowFlakes = []; 18 19/** 20 * @type SnowAccumulator[] 21 */ 22const floor = []; 23 24const sectionSize = Config.canvas.width / Config.groundAccumulator.slices; 25/** 26 * @type {HTMLCanvasElement} 27 */ 28let canvas; 29 30/** 31 * @type {CanvasRenderingContext2D} 32 */ 33let ctx; 34 35/** 36 * @type {Plow} 37 */ 38let plow; 39 40let lastSpawn = 0; 41let lastFrame = 0; 42 43let isPlowing = false; 44 45window.onload = init; 46 47function init() { 48 // @ts-ignore -- I dunno how to get ts / jsdoc to be ok with this 49 canvas = document.getElementById("main"); 50 51 ctx = canvas.getContext("2d"); 52 canvas.width = Config.canvas.width; 53 canvas.height = Config.canvas.height; 54 ctx.imageSmoothingEnabled = false; 55 56 for (let i = 0; i < Config.groundAccumulator.slices; i++) { 57 floor.push( 58 createSnowAccumulator(i * sectionSize, Config.canvas.height, sectionSize), 59 ); 60 } 61 62 window.requestAnimationFrame(gameLoop); 63} 64 65/** 66 * @param time {DOMHighResTimeStamp} 67 */ 68function gameLoop(time) { 69 const delta = time - lastFrame; 70 71 ctx.clearRect(0, 0, Config.canvas.width, Config.canvas.height); 72 if (time - lastSpawn > 500 && snowFlakes.length < Config.snow.max) { 73 for (let i = 0; i < Math.floor(Math.random() * 2); i++) { 74 snowFlakes.push(createSnowflake()); 75 } 76 } 77 78 snowFlakes.forEach((flake, idx) => { 79 moveSnowflake(flake, delta); 80 const floorIdx = Math.floor(flake.x / sectionSize); 81 82 if (snowAccumulatorCollisionY(floor[floorIdx], flake)) { 83 if (!isPlowing) { 84 accumulateSnow(floor[floorIdx], flake); 85 } 86 snowFlakes[idx] = createSnowflake(); 87 } 88 drawSnowflake(flake, ctx); 89 }); 90 91 const avgHeight = 92 floor.reduce((acc, sa) => acc + sa.height, 0) / floor.length; 93 94 if (avgHeight > Config.groundAccumulator.max) { 95 isPlowing = true; 96 } 97 98 drawSnowAccumulators(ctx, floor, plow); 99 100 if (isPlowing) { 101 plow ??= createPlow(); 102 movePlow(plow, delta); 103 drawPlow(plow, ctx); 104 if (plowDone(plow)) { 105 isPlowing = false; 106 plow = null; 107 floor.forEach((sa) => resetSnowAccumulator(sa)); 108 } 109 } 110 111 lastFrame = time; 112 window.requestAnimationFrame(gameLoop); 113}