refactor: add jsdoc, improve code redability

This commit is contained in:
Timofey Gelazoniya 2025-01-28 19:50:28 +03:00
parent 618fc16ed8
commit 4a6aa94e39
Signed by: zeldon
GPG Key ID: 047886915281DD2A
4 changed files with 1202 additions and 876 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,98 +1,135 @@
import { event_listener_array, calc_delta_time } from './utils'; import { addEventListeners, calcDeltaTime } from "./utils";
export function perspective_3d(class_name) { /**
const elements = document.body.getElementsByClassName(class_name); * Adds 3D perspective rotation effect to elements with the specified class name.
* The rotation is controlled by mouse/touch movement and uses spring physics for smooth animation.
* @param {string} className - The class name of elements to apply the 3D effect to
*/
export const add3DRotationEffect = (className) => {
const elements = document.body.getElementsByClassName(className);
let velocity_x = 0; const state = {
let velocity_y = 0; velocity: { x: 0, y: 0 },
let rotation_x = 0; rotation: { x: 0, y: 0 },
let rotation_y = 0; targetRotation: { x: 0, y: 0 },
let target_rotation_x = 0; };
let target_rotation_y = 0;
(function update() { /**
let dt = calc_delta_time(); * Calculates spring physics for smooth animation
const ss = .08; * @param {number} position - Current position
* @param {number} target - Target position
* @param {number} velocity - Current velocity
* @param {number} omega - Angular frequency
* @param {number} dt - Delta time
* @returns {number} New velocity
*/
const spring = (position, target, velocity, omega, dt) => {
const n1 = velocity - (position - target) * (omega ** 2 * dt);
const n2 = 1 + omega * dt;
return n1 / n2 ** 2;
};
velocity_x = spring(rotation_x, target_rotation_x, velocity_x, 10, dt); /**
velocity_y = spring(rotation_y, target_rotation_y, velocity_y, 10, dt); * Clamps a value between min and max
rotation_x += velocity_x * dt * ss; * @param {number} value - Value to clamp
rotation_y += velocity_y * dt * ss; * @param {number} min - Minimum value
* @param {number} max - Maximum value
* @returns {number} Clamped value
*/
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
const style = `perspective(700px) rotateX(${rotation_y}rad) rotateY(${rotation_x}rad)`; /**
* Updates rotation values and applies transform styles in animation frame
*/
const updateRotation = () => {
const dt = calcDeltaTime();
const springConstant = 0.08;
for (const el of elements) { state.velocity.x = spring(
el.style.transform = style; state.rotation.x,
} state.targetRotation.x,
state.velocity.x,
10,
dt
);
state.velocity.y = spring(
state.rotation.y,
state.targetRotation.y,
state.velocity.y,
10,
dt
);
requestAnimationFrame(update); state.rotation.x += state.velocity.x * dt * springConstant;
})(); state.rotation.y += state.velocity.y * dt * springConstant;
event_listener_array(window, ["mousemove", "touchmove"], (e) => { const style = `perspective(700px) rotateX(${state.rotation.y}rad) rotateY(${state.rotation.x}rad)`;
if (e.changedTouches && e.changedTouches[0]) { Array.from(elements).forEach((el) => (el.style.transform = style));
e = e.changedTouches[0];
}
target_rotation_x = (e.clientX / window.innerWidth) * 2 - 1; requestAnimationFrame(updateRotation);
target_rotation_y = -(e.clientY / window.innerHeight) * 2 + 1; };
target_rotation_x = clamp(target_rotation_x, -0.5, 0.5); /**
target_rotation_y = clamp(target_rotation_y, -0.5, 0.5); * Handles mouse/touch movement to update target rotation
}, false); * @param {(MouseEvent|TouchEvent)} e - Mouse or touch event
*/
const handleMovement = (e) => {
const event = e.changedTouches?.[0] || e;
function spring(position, target, velocity, omega, dt) { state.targetRotation.x = (event.clientX / window.innerWidth) * 2 - 1;
let n1 = velocity - (position - target) * (Math.pow(omega, 2) * dt); state.targetRotation.y = -(event.clientY / window.innerHeight) * 2 + 1;
let n2 = 1 + omega * dt;
return n1 / Math.pow(n2, 2);
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
}
(function pick_greeting() { state.targetRotation.x = clamp(state.targetRotation.x, -0.5, 0.5);
state.targetRotation.y = clamp(state.targetRotation.y, -0.5, 0.5);
};
addEventListeners(window, ["mousemove", "touchmove"], handleMovement, false);
updateRotation();
};
/**
* Updates greeting text based on time of day.
* Updates every minute.
*/
export const updateGreeting = () => {
const hours = new Date().getHours(); const hours = new Date().getHours();
const greeing_el = document.querySelector(".greeting"); const greetingEl = document.querySelector(".greeting");
if (!greetingEl) return;
if (hours < 6) { /**
const data = "Good night"; * Gets appropriate greeting based on hour of day
greeing_el.textContent = data; * @param {number} hours - Hour in 24-hour format
greeing_el.innerText = data; * @returns {string} Greeting text
} else if (hours >= 6 && hours < 12) { */
const data = "Good morning"; const getGreeting = (hours) => {
greeing_el.textContent = data; if (hours < 6) return "Good night";
greeing_el.innerText = data; if (hours < 12) return "Good morning";
} else if (hours >= 12 && hours < 16) { if (hours < 16) return "Good afternoon";
const data = "Good afternoon"; if (hours <= 23) return "Good evening";
greeing_el.textContent = data; return "Hello";
greeing_el.innerText = data; };
} else if (hours >= 16 && hours <= 23) {
const data = "Good evening";
greeing_el.textContent = data;
greeing_el.innerText = data;
} else {
const data = "Hello";
greeing_el.textContent = data;
greeing_el.innerText = data;
}
setTimeout(pick_greeting, 6e4); const greeting = getGreeting(hours);
})(); greetingEl.textContent = greeting;
setTimeout(updateGreeting, 60_000); // 60 seconds
};
/**
* Updates clock display with current time in HH:MM:SS format.
* Updates every second.
*/
export const updateTime = () => {
const clockEl = document.querySelector(".clock");
if (!clockEl) return;
(function render_time() {
const time = new Date(); const time = new Date();
let h = time.getHours(); const formatted_date = [time.getHours(), time.getMinutes(), time.getSeconds()]
.map((num) => num.toString().padStart(2, "0"))
.join(":");
h = h.toString().padStart(2, 0); clockEl.textContent = formatted_date;
let m = time.getMinutes().toString().padStart(2, 0);
let s = time.getSeconds().toString().padStart(2, 0);
const clock_el = document.querySelector('.clock'); setTimeout(updateTime, 1000);
if (!clock_el) return; };
const formatted_date = `${h}:${m}:${s}`;
clock_el.textContent = formatted_date;
clock_el.innerText = formatted_date;
setTimeout(render_time, 1e3);
})();

View File

@ -1,18 +1,17 @@
// CSS Import import "./css/style.css";
import './css/style.css'; import "./css/font.css";
import './css/font.css'; import "./css/animation.css";
import './css/animation.css'; import "./css/button.css";
import './css/button.css';
// JS Import import "./app";
import './app'; import { add3DRotationEffect, updateGreeting, updateTime } from "./app";
import { importJsAsModule } from "./utils";
import { perspective_3d } from './app'; const initialize = async () => {
import { import_js_as_module } from './utils'; await importJsAsModule("/fluid.js");
add3DRotationEffect("perspective");
updateGreeting();
updateTime();
};
window.onload = start; window.addEventListener("load", initialize);
async function start() {
await import_js_as_module('/fluid.js');
perspective_3d('perspective');
}

View File

@ -1,31 +1,57 @@
/** Timestamp of the last update used for delta time calculation */
export let last_update = Date.now(); export let last_update = Date.now();
export function calc_delta_time() { /**
let now = Date.now(); * Calculates the time elapsed since the last update, capped at 60fps
let dt = (now - last_update) / 1e3; * @returns {number} Delta time in seconds
dt = Math.min(dt, 0.016); */
export function calcDeltaTime() {
const now = Date.now();
const MAX_DELTA = 0.016; // 60fps cap
let dt = (now - last_update) / 1000;
dt = Math.min(dt, MAX_DELTA);
last_update = now; last_update = now;
return dt; return dt;
} }
export function event_listener_array(whos, event_names, callback, options = null) { /**
if (!Array.isArray(whos)) { * Adds event listeners to one or more elements for multiple event types
whos = [whos]; * @param {HTMLElement|HTMLElement[]} elements - Single element or array of elements
} * @param {string[]} eventNames - Array of event names to listen for
for (const name of event_names) { * @param {Function} callback - Event handler function
for (const who of whos) { * @param {(boolean|AddEventListenerOptions|null)} [options=null] - Event listener options
who.addEventListener(name, callback, options); */
} export function addEventListeners(
} elements,
} eventNames,
callback,
options = null
) {
// Convert single element to array if needed
const elementArray = Array.isArray(elements) ? elements : [elements];
export async function import_js_as_module(url) { // Add event listeners to each element for each event name
return new Promise((resole, reject) => { eventNames.forEach((eventName) => {
const body = document.getElementsByTagName('body')[0]; elementArray.forEach((element) => {
const script = document.createElement('script'); element.addEventListener(eventName, callback, options);
script.type = 'module'; });
script.src = url;
script.onload = resole;
body.appendChild(script);
}); });
} }
/**
* Imports a JavaScript file as an ES module by injecting a script tag
* @param {string} url - URL of the JavaScript file to import
* @returns {Promise<void>} Resolves when the script has loaded
*/
export const importJsAsModule = async (url) => {
return new Promise((resolve, reject) => {
const body = document.querySelector("body");
const script = document.createElement("script");
script.type = "module";
script.src = url;
script.onload = resolve;
body.appendChild(script);
});
};