mirror of
https://github.com/xzeldon/zeldon-site.git
synced 2025-01-31 15:47:27 +03:00
refactor: add jsdoc, improve code redability
This commit is contained in:
parent
618fc16ed8
commit
4a6aa94e39
1778
public/fluid.js
1778
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;
|
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);
|
||||||
const hours = new Date().getHours();
|
state.targetRotation.y = clamp(state.targetRotation.y, -0.5, 0.5);
|
||||||
const greeing_el = document.querySelector(".greeting");
|
};
|
||||||
|
|
||||||
if (hours < 6) {
|
addEventListeners(window, ["mousemove", "touchmove"], handleMovement, false);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(pick_greeting, 6e4);
|
updateRotation();
|
||||||
})();
|
};
|
||||||
|
|
||||||
(function render_time() {
|
/**
|
||||||
const time = new Date();
|
* Updates greeting text based on time of day.
|
||||||
let h = time.getHours();
|
* Updates every minute.
|
||||||
|
*/
|
||||||
|
export const updateGreeting = () => {
|
||||||
|
const hours = new Date().getHours();
|
||||||
|
const greetingEl = document.querySelector(".greeting");
|
||||||
|
if (!greetingEl) return;
|
||||||
|
|
||||||
h = h.toString().padStart(2, 0);
|
/**
|
||||||
let m = time.getMinutes().toString().padStart(2, 0);
|
* Gets appropriate greeting based on hour of day
|
||||||
let s = time.getSeconds().toString().padStart(2, 0);
|
* @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";
|
||||||
|
};
|
||||||
|
|
||||||
const clock_el = document.querySelector('.clock');
|
const greeting = getGreeting(hours);
|
||||||
if (!clock_el) return;
|
greetingEl.textContent = greeting;
|
||||||
|
|
||||||
const formatted_date = `${h}:${m}:${s}`;
|
setTimeout(updateGreeting, 60_000); // 60 seconds
|
||||||
clock_el.textContent = formatted_date;
|
};
|
||||||
clock_el.innerText = formatted_date;
|
|
||||||
|
|
||||||
setTimeout(render_time, 1e3);
|
/**
|
||||||
})();
|
* 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;
|
||||||
|
|
||||||
|
const time = new Date();
|
||||||
|
const formatted_date = [time.getHours(), time.getMinutes(), time.getSeconds()]
|
||||||
|
.map((num) => num.toString().padStart(2, "0"))
|
||||||
|
.join(":");
|
||||||
|
|
||||||
|
clockEl.textContent = formatted_date;
|
||||||
|
|
||||||
|
setTimeout(updateTime, 1000);
|
||||||
|
};
|
||||||
|
29
src/main.js
29
src/main.js
@ -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');
|
|
||||||
}
|
|
||||||
|
76
src/utils.js
76
src/utils.js
@ -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);
|
*/
|
||||||
last_update = now;
|
export function calcDeltaTime() {
|
||||||
return dt;
|
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)) {
|
* 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];
|
||||||
|
|
||||||
|
// Add event listeners to each element for each event name
|
||||||
|
eventNames.forEach((eventName) => {
|
||||||
|
elementArray.forEach((element) => {
|
||||||
|
element.addEventListener(eventName, callback, options);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function import_js_as_module(url) {
|
/**
|
||||||
return new Promise((resole, reject) => {
|
* Imports a JavaScript file as an ES module by injecting a script tag
|
||||||
const body = document.getElementsByTagName('body')[0];
|
* @param {string} url - URL of the JavaScript file to import
|
||||||
const script = document.createElement('script');
|
* @returns {Promise<void>} Resolves when the script has loaded
|
||||||
script.type = 'module';
|
*/
|
||||||
script.src = url;
|
export const importJsAsModule = async (url) => {
|
||||||
script.onload = resole;
|
return new Promise((resolve, reject) => {
|
||||||
body.appendChild(script);
|
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