A browser source overlay for winter vibes for your Live Streams or Videos

JSDOC and got rid of typescript - we don't need it for something this simple

+14
README.md
···
+
# winterVibesJS
+
No build JS twitch wintery overlay.
+
+
Run locally and tweak or add the Github page (COMING SOON) as a browser source.
+
+
## 0 Dependencies required!
+
This project has no required dependencies.
+
+
The only dependency in package.json is vite for HMR purposes while developing.
+
+
Everything uses JSDoc
+
+
+
# THIS IS NOT DONE YET AND IS A WIP
-4
config.ts
···
-
export const CANVAS_WIDTH = 320;
-
export const CANVAS_HEIGHT = 180;
-
export const FLOOR_RAISE_THRESHOLD = 200;
-
export const MAX_PARTICAL_COUNT = 500;
-27
floor.ts
···
-
import { CANVAS_HEIGHT, CANVAS_WIDTH, FLOOR_RAISE_THRESHOLD } from "./config";
-
import Snowflake from "./snowflake";
-
-
export default class Floor {
-
y: number = CANVAS_HEIGHT;
-
accumulator = 0;
-
-
checkColission(flake: Snowflake): Boolean {
-
if (flake.y >= this.y) {
-
this.accumulator++;
-
-
if (this.accumulator % FLOOR_RAISE_THRESHOLD == 0) {
-
this.y -= 1;
-
console.log("floor raised");
-
}
-
return true;
-
}
-
return false;
-
}
-
-
draw(ctx: CanvasRenderingContext2D) {
-
ctx.beginPath();
-
ctx.rect(0, this.y, CANVAS_WIDTH, CANVAS_HEIGHT - this.y);
-
ctx.fillStyle = "#fff";
-
ctx.fill();
-
}
-
}
+2 -2
index.html
···
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
-
<title>Winter Vibes</title>
+
<title>Winter Vibes JS</title>
</head>
<body>
<canvas id="main"></canvas>
-
<script type="module" src="/main.ts"></script>
+
<script type="module" src="/src/main.js"></script>
</body>
</html>
-46
main.ts
···
-
import "./style.css";
-
import Snowflake from "./snowflake";
-
import { CANVAS_HEIGHT, CANVAS_WIDTH, MAX_PARTICAL_COUNT } from "./config";
-
import Floor from "./floor";
-
-
let canvas: HTMLCanvasElement;
-
let context: CanvasRenderingContext2D;
-
let lastSpawn: DOMHighResTimeStamp = 0;
-
let floor = new Floor();
-
let lastFrame: number = 0;
-
let fps = 0;
-
const snowFlakes: Snowflake[] = [];
-
-
window.onload = init;
-
-
function init() {
-
canvas = document.getElementById("main") as HTMLCanvasElement;
-
context = canvas.getContext("2d")!;
-
canvas.width = CANVAS_WIDTH;
-
canvas.height = CANVAS_HEIGHT;
-
-
window.requestAnimationFrame(gameLoop);
-
}
-
-
function gameLoop(delta: DOMHighResTimeStamp) {
-
fps = Math.round(1 / ((delta - lastFrame) / 1000));
-
context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
-
if (delta - lastSpawn > 500 && snowFlakes.length < MAX_PARTICAL_COUNT) {
-
for (let i = 0; i < Math.floor(Math.random() * 2); i++) {
-
snowFlakes.push(new Snowflake());
-
}
-
}
-
-
snowFlakes.forEach((flake) => {
-
flake.move();
-
if (floor.checkColission(flake)) {
-
flake.reset();
-
}
-
flake.draw(context);
-
});
-
-
floor.draw(context);
-
context.fillText("FPS: " + fps, 10, 30);
-
lastFrame = delta;
-
window.requestAnimationFrame(gameLoop);
-
}
+2 -3
package.json
···
{
"name": "wintervibesjs",
-
"private": true,
-
"version": "0.0.0",
+
"private": false,
+
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
···
"preview": "vite preview"
},
"devDependencies": {
-
"typescript": "^5.6.3",
"vite": "^5.4.10"
}
}
-42
snowflake.ts
···
-
import { CANVAS_WIDTH } from "./config";
-
-
export default class Snowflake {
-
x: number;
-
y: number;
-
size: number;
-
speed: number;
-
-
constructor() {
-
this.x = Math.floor(Math.random() * CANVAS_WIDTH);
-
this.y = -1;
-
this.speed = Math.random();
-
this.size = Math.floor(Math.random() * 4);
-
}
-
-
reset() {
-
this.x = Math.floor(Math.random() * CANVAS_WIDTH);
-
this.y = -1;
-
this.speed = Math.random();
-
this.size = Math.floor(Math.random() * 4);
-
}
-
-
draw(context: CanvasRenderingContext2D) {
-
context.beginPath();
-
context.rect(this.x, this.y, this.size, this.size);
-
context.fillStyle = "#fff";
-
context.fill();
-
}
-
-
move() {
-
this.y += this.speed;
-
const sway = Math.random() < 0.5;
-
if (sway) {
-
const direction = Math.random() < 0.5;
-
if (direction) {
-
this.x += (this.speed * 5) / 10;
-
} else {
-
this.x -= (this.speed * 5) / 10;
-
}
-
}
-
}
-
}
+24
src/config.js
···
+
// canvas size - I chose these sizes for 16:9 aspect ratio and really pixely
+
export const CANVAS_WIDTH = 320;
+
export const CANVAS_HEIGHT = 180;
+
+
// number of collisions required to shift the snowlevel up
+
export const FLOOR_RAISE_THRESHOLD = 100;
+
+
// number of snowflakes to render
+
export const MAX_PARTICAL_COUNT = 200;
+
+
// minimum size of snowflake
+
export const MIN_SIZE = 1;
+
+
// maximum size of snowflake
+
export const MAX_SIZE = 4;
+
+
// maximum speed of a falling snowflake
+
export const MAX_SPEED = 1;
+
+
// minimum speed of a snowflake falling
+
export const MIN_SPEED = 0.5;
+
+
// threshold for how fast snowflakes can go and have horizontal movement
+
export const SWAY_THRESHOLD = 0.8;
+69
src/main.js
···
+
import "./style.css";
+
import { CANVAS_HEIGHT, CANVAS_WIDTH, MAX_PARTICAL_COUNT } from "./config";
+
import { createSnowflake, drawSnowflake, moveSnowflake } from "./snowflake";
+
import {
+
checkCollision,
+
createSnowAccumulator,
+
drawSnowAccumulator,
+
} from "./snow_accumulator";
+
+
/**
+
* @type {HTMLCanvasElement}
+
*/
+
let canvas;
+
+
/**
+
* @type {CanvasRenderingContext2D}
+
*/
+
let ctx;
+
+
/**
+
* @type{Array.<Snowflake>}
+
*/
+
const snowFlakes = [];
+
+
let lastSpawn = 0;
+
let floor = createSnowAccumulator(CANVAS_HEIGHT);
+
+
let lastFrame = 0;
+
let fps = 0;
+
+
window.onload = init;
+
+
function init() {
+
// @ts-ignore -- I dunno how to get ts / jsdoc to be ok with this
+
canvas = document.getElementById("main");
+
+
ctx = canvas.getContext("2d");
+
canvas.width = CANVAS_WIDTH;
+
canvas.height = CANVAS_HEIGHT;
+
+
window.requestAnimationFrame(gameLoop);
+
}
+
+
/**
+
* @param delta {DOMHighResTimeStamp}
+
*/
+
function gameLoop(delta) {
+
fps = Math.round(1 / ((delta - lastFrame) / 1000));
+
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+
if (delta - lastSpawn > 500 && snowFlakes.length < MAX_PARTICAL_COUNT) {
+
for (let i = 0; i < Math.floor(Math.random() * 2); i++) {
+
snowFlakes.push(createSnowflake());
+
}
+
}
+
+
snowFlakes.forEach((flake, idx) => {
+
moveSnowflake(flake);
+
if (checkCollision(floor, flake)) {
+
snowFlakes[idx] = createSnowflake();
+
}
+
drawSnowflake(flake, ctx);
+
});
+
+
drawSnowAccumulator(ctx, floor);
+
ctx.fillText("FPS: " + fps, 10, 30);
+
lastFrame = delta;
+
+
window.requestAnimationFrame(gameLoop);
+
}
+40
src/snow_accumulator.js
···
+
import { CANVAS_HEIGHT, CANVAS_WIDTH, FLOOR_RAISE_THRESHOLD } from "./config";
+
+
/**
+
* @param {number} y
+
* @returns {SnowAccumulator}
+
*/
+
export function createSnowAccumulator(y) {
+
return {
+
accumulator: 0,
+
y,
+
};
+
}
+
+
/**
+
* @param {SnowAccumulator} sa
+
* @param {Object} item
+
* @param {number} item.y
+
*/
+
export function checkCollision(sa, { y }) {
+
if (y >= sa.y) {
+
sa.accumulator++;
+
+
if (sa.accumulator % FLOOR_RAISE_THRESHOLD == 0) {
+
sa.y -= 1;
+
}
+
return true;
+
}
+
return false;
+
}
+
+
/**
+
* @param {CanvasRenderingContext2D} ctx
+
* @param {SnowAccumulator} sa
+
*/
+
export function drawSnowAccumulator(ctx, sa) {
+
ctx.beginPath();
+
ctx.rect(0, sa.y, CANVAS_WIDTH, CANVAS_HEIGHT - sa.y);
+
ctx.fillStyle = "#fff";
+
ctx.fill();
+
}
+50
src/snowflake.js
···
+
import {
+
CANVAS_WIDTH,
+
MAX_SIZE,
+
MAX_SPEED,
+
MIN_SIZE,
+
MIN_SPEED,
+
SWAY_THRESHOLD,
+
} from "./config";
+
+
/**
+
* @returns {Snowflake}
+
*/
+
export function createSnowflake() {
+
return {
+
x: Math.floor(Math.random() * CANVAS_WIDTH),
+
y: -1,
+
speed: Math.random() * (MIN_SPEED - MAX_SPEED) + MAX_SPEED,
+
size: Math.floor(Math.random() * (MIN_SIZE - MAX_SIZE) + MAX_SIZE),
+
};
+
}
+
+
/**
+
* @param {Object} snowflake
+
* @param {number} snowflake.x
+
* @param {number} snowflake.y
+
* @param {number} snowflake.size
+
* @param {CanvasRenderingContext2D} ctx
+
*/
+
export function drawSnowflake({ x, y, size }, ctx) {
+
ctx.beginPath();
+
ctx.rect(x, y, size, size);
+
ctx.fillStyle = "#fff";
+
ctx.fill();
+
}
+
+
/**
+
* @param {Snowflake} snowflake
+
*/
+
export function moveSnowflake(snowflake) {
+
snowflake.y += snowflake.speed;
+
const sway = Math.random() < 0.5 && snowflake.speed < SWAY_THRESHOLD;
+
if (sway) {
+
const direction = Math.random() < 0.5;
+
if (direction) {
+
snowflake.x += (snowflake.speed * 5) / 10;
+
} else {
+
snowflake.x -= (snowflake.speed * 5) / 10;
+
}
+
}
+
}
+14
src/types.d.ts
···
+
export {};
+
declare global {
+
type Snowflake = {
+
x: number;
+
y: number;
+
speed: number;
+
size: number;
+
};
+
+
type SnowAccumulator = {
+
accumulator: number;
+
y: number;
+
};
+
}
style.css src/style.css
+13 -107
tsconfig.json
···
{
"compilerOptions": {
-
/* Visit https://aka.ms/tsconfig to read more about this file */
-
-
/* Projects */
-
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
-
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
-
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
-
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
-
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
-
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
-
-
/* Language and Environment */
-
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
-
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
-
// "jsx": "preserve", /* Specify what JSX code is generated. */
-
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
-
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
-
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
-
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
-
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
-
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
-
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
-
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
-
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
-
-
/* Modules */
-
"module": "commonjs", /* Specify what module code is generated. */
-
// "rootDir": "./", /* Specify the root folder within your source files. */
-
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
-
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
-
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
-
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
-
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
-
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
-
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
-
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
-
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
-
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
-
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
-
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
-
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
-
// "resolveJsonModule": true, /* Enable importing .json files. */
-
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
-
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
-
-
/* JavaScript Support */
-
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
-
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
-
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
-
-
/* Emit */
-
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
-
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
-
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
-
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
-
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
-
// "noEmit": true, /* Disable emitting files from a compilation. */
-
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
-
// "outDir": "./", /* Specify an output folder for all emitted files. */
-
// "removeComments": true, /* Disable emitting comments. */
-
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
-
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
-
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
-
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
-
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
-
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
-
// "newLine": "crlf", /* Set the newline character for emitting files. */
-
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
-
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
-
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
-
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
-
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
-
-
/* Interop Constraints */
-
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
-
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
-
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
-
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
-
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
-
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
-
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
-
-
/* Type Checking */
-
"strict": true, /* Enable all strict type-checking options. */
-
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
-
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
-
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
-
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
-
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
-
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
-
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
-
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
-
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
-
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
-
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
-
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
-
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
-
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
-
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
-
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
-
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
-
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
-
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
-
-
/* Completeness */
-
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
-
"skipLibCheck": true /* Skip type checking all .d.ts files. */
-
}
+
"allowJs": true,
+
"checkJs": true,
+
"noEmit": true,
+
"moduleResolution": "node",
+
"target": "ESNext",
+
"module": "ESNext",
+
"types": [
+
"./src/types"
+
]
+
},
+
"include": [
+
"**/*.js"
+
]
}