mirror of
https://github.com/xzeldon/zeldon-site.git
synced 2025-01-31 12:27:26 +03:00
refactor: add jsdoc, improve code redability
This commit is contained in:
parent
618fc16ed8
commit
4a6aa94e39
584
public/fluid.js
584
public/fluid.js
File diff suppressed because it is too large
Load Diff
195
src/app.js
195
src/app.js
@ -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;
|
||||
let velocity_y = 0;
|
||||
let rotation_x = 0;
|
||||
let rotation_y = 0;
|
||||
let target_rotation_x = 0;
|
||||
let target_rotation_y = 0;
|
||||
const state = {
|
||||
velocity: { x: 0, y: 0 },
|
||||
rotation: { x: 0, y: 0 },
|
||||
targetRotation: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
(function update() {
|
||||
let dt = calc_delta_time();
|
||||
const ss = .08;
|
||||
/**
|
||||
* Calculates spring physics for smooth animation
|
||||
* @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);
|
||||
rotation_x += velocity_x * dt * ss;
|
||||
rotation_y += velocity_y * dt * ss;
|
||||
/**
|
||||
* Clamps a value between min and max
|
||||
* @param {number} value - Value to clamp
|
||||
* @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) {
|
||||
el.style.transform = style;
|
||||
}
|
||||
state.velocity.x = spring(
|
||||
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) => {
|
||||
if (e.changedTouches && e.changedTouches[0]) {
|
||||
e = e.changedTouches[0];
|
||||
}
|
||||
const style = `perspective(700px) rotateX(${state.rotation.y}rad) rotateY(${state.rotation.x}rad)`;
|
||||
Array.from(elements).forEach((el) => (el.style.transform = style));
|
||||
|
||||
target_rotation_x = (e.clientX / window.innerWidth) * 2 - 1;
|
||||
target_rotation_y = -(e.clientY / window.innerHeight) * 2 + 1;
|
||||
requestAnimationFrame(updateRotation);
|
||||
};
|
||||
|
||||
target_rotation_x = clamp(target_rotation_x, -0.5, 0.5);
|
||||
target_rotation_y = clamp(target_rotation_y, -0.5, 0.5);
|
||||
}, false);
|
||||
/**
|
||||
* Handles mouse/touch movement to update target rotation
|
||||
* @param {(MouseEvent|TouchEvent)} e - Mouse or touch event
|
||||
*/
|
||||
const handleMovement = (e) => {
|
||||
const event = e.changedTouches?.[0] || e;
|
||||
|
||||
function spring(position, target, velocity, omega, dt) {
|
||||
let n1 = velocity - (position - target) * (Math.pow(omega, 2) * dt);
|
||||
let n2 = 1 + omega * dt;
|
||||
return n1 / Math.pow(n2, 2);
|
||||
}
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
}
|
||||
state.targetRotation.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
state.targetRotation.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
|
||||
(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 greeing_el = document.querySelector(".greeting");
|
||||
const greetingEl = document.querySelector(".greeting");
|
||||
if (!greetingEl) return;
|
||||
|
||||
if (hours < 6) {
|
||||
const data = "Good night";
|
||||
greeing_el.textContent = data;
|
||||
greeing_el.innerText = data;
|
||||
} else if (hours >= 6 && hours < 12) {
|
||||
const data = "Good morning";
|
||||
greeing_el.textContent = data;
|
||||
greeing_el.innerText = data;
|
||||
} else if (hours >= 12 && hours < 16) {
|
||||
const data = "Good afternoon";
|
||||
greeing_el.textContent = data;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Gets appropriate greeting based on hour of day
|
||||
* @param {number} hours - Hour in 24-hour format
|
||||
* @returns {string} Greeting text
|
||||
*/
|
||||
const getGreeting = (hours) => {
|
||||
if (hours < 6) return "Good night";
|
||||
if (hours < 12) return "Good morning";
|
||||
if (hours < 16) return "Good afternoon";
|
||||
if (hours <= 23) return "Good evening";
|
||||
return "Hello";
|
||||
};
|
||||
|
||||
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();
|
||||
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);
|
||||
let m = time.getMinutes().toString().padStart(2, 0);
|
||||
let s = time.getSeconds().toString().padStart(2, 0);
|
||||
clockEl.textContent = formatted_date;
|
||||
|
||||
const clock_el = document.querySelector('.clock');
|
||||
if (!clock_el) return;
|
||||
|
||||
const formatted_date = `${h}:${m}:${s}`;
|
||||
clock_el.textContent = formatted_date;
|
||||
clock_el.innerText = formatted_date;
|
||||
|
||||
setTimeout(render_time, 1e3);
|
||||
})();
|
||||
setTimeout(updateTime, 1000);
|
||||
};
|
||||
|
29
src/main.js
29
src/main.js
@ -1,18 +1,17 @@
|
||||
// CSS Import
|
||||
import './css/style.css';
|
||||
import './css/font.css';
|
||||
import './css/animation.css';
|
||||
import './css/button.css';
|
||||
import "./css/style.css";
|
||||
import "./css/font.css";
|
||||
import "./css/animation.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';
|
||||
import { import_js_as_module } from './utils';
|
||||
const initialize = async () => {
|
||||
await importJsAsModule("/fluid.js");
|
||||
add3DRotationEffect("perspective");
|
||||
updateGreeting();
|
||||
updateTime();
|
||||
};
|
||||
|
||||
window.onload = start;
|
||||
|
||||
async function start() {
|
||||
await import_js_as_module('/fluid.js');
|
||||
perspective_3d('perspective');
|
||||
}
|
||||
window.addEventListener("load", initialize);
|
||||
|
70
src/utils.js
70
src/utils.js
@ -1,31 +1,57 @@
|
||||
/** Timestamp of the last update used for delta time calculation */
|
||||
export let last_update = Date.now();
|
||||
|
||||
export function calc_delta_time() {
|
||||
let now = Date.now();
|
||||
let dt = (now - last_update) / 1e3;
|
||||
dt = Math.min(dt, 0.016);
|
||||
/**
|
||||
* Calculates the time elapsed since the last update, capped at 60fps
|
||||
* @returns {number} Delta time in seconds
|
||||
*/
|
||||
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;
|
||||
return dt;
|
||||
}
|
||||
|
||||
export function event_listener_array(whos, event_names, callback, options = null) {
|
||||
if (!Array.isArray(whos)) {
|
||||
whos = [whos];
|
||||
}
|
||||
for (const name of event_names) {
|
||||
for (const who of whos) {
|
||||
who.addEventListener(name, callback, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Adds event listeners to one or more elements for multiple event types
|
||||
* @param {HTMLElement|HTMLElement[]} elements - Single element or array of elements
|
||||
* @param {string[]} eventNames - Array of event names to listen for
|
||||
* @param {Function} callback - Event handler function
|
||||
* @param {(boolean|AddEventListenerOptions|null)} [options=null] - Event listener 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) {
|
||||
return new Promise((resole, reject) => {
|
||||
const body = document.getElementsByTagName('body')[0];
|
||||
const script = document.createElement('script');
|
||||
script.type = 'module';
|
||||
script.src = url;
|
||||
script.onload = resole;
|
||||
body.appendChild(script);
|
||||
// Add event listeners to each element for each event name
|
||||
eventNames.forEach((eventName) => {
|
||||
elementArray.forEach((element) => {
|
||||
element.addEventListener(eventName, callback, options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user