140 lines
4.5 KiB
JavaScript
Executable File
140 lines
4.5 KiB
JavaScript
Executable File
const FAN_SPEED = 2;
|
|
|
|
const textElem = document.getElementById("text");
|
|
const keyElems = document.getElementsByClassName("key");
|
|
const fanElem = document.getElementById("fan");
|
|
const cursorElem = document.getElementById("cursor");
|
|
const fanPos = { x: 50, y: 50 };
|
|
const cursorPos = { x: 200, y: 112 };
|
|
const cursorVel = { x: 0, y: 0 };
|
|
let isFanClicked = false;
|
|
|
|
fanElem.addEventListener('dragstart', (e) => e.preventDefault());
|
|
fanElem.addEventListener("mousedown", () => isFanClicked = true);
|
|
fanElem.addEventListener("mouseup", () => isFanClicked = false);
|
|
document.addEventListener("mouseleave", () => isFanClicked = false);
|
|
|
|
document.addEventListener("mousemove", (e) => {
|
|
if (!isFanClicked) return;
|
|
fanPos.x = e.pageX - fanElem.clientWidth/2;
|
|
fanPos.y = e.pageY - fanElem.clientHeight/2;
|
|
if (fanPos.x < -fanElem.clientWidth/2) {
|
|
fanPos.x = -fanElem.clientWidth/2;
|
|
} else if (fanPos.x > document.body.clientWidth - fanElem.clientWidth/2) {
|
|
fanPos.x = document.body.clientWidth - fanElem.clientWidth/2;
|
|
}
|
|
if (fanPos.y < -fanElem.clientHeight/2) {
|
|
fanPos.y = -fanElem.clientHeight/2;
|
|
} else if (fanPos.y > document.body.clientHeight - fanElem.clientHeight/2) {
|
|
fanPos.y = document.body.clientHeight - fanElem.clientHeight/2;
|
|
}
|
|
});
|
|
|
|
function blowCursor(deltaTime) {
|
|
// direction from the fan to the fan cursor
|
|
const direction = {
|
|
x: (cursorPos.x + cursorElem.clientWidth/2) - (fanPos.x + fanElem.clientWidth/2),
|
|
y: (cursorPos.y + cursorElem.clientHeight/2) - (fanPos.y + fanElem.clientHeight/2)
|
|
};
|
|
// distance between the fan and the fan cursor
|
|
const distance = Math.sqrt(Math.pow(direction.x, 2) + Math.pow(direction.y, 2));
|
|
// make the direction a unit vector
|
|
direction.x /= distance;
|
|
direction.y /= distance;
|
|
// calculate the force and apply it
|
|
const force = Math.pow(FAN_SPEED / 10, 2) / distance;
|
|
cursorVel.x += force * direction.x * deltaTime;
|
|
cursorVel.y += force * direction.y * deltaTime;
|
|
}
|
|
|
|
function decelerateCursor(deltaTime) {
|
|
// apply the viscous friction formula
|
|
const accX = 0.1 * Math.pow(cursorVel.x, 2) / 2;
|
|
const accY = 0.1 * Math.pow(cursorVel.y, 2) / 2;
|
|
if (Math.round(cursorVel.x * 1000) == 0) {
|
|
cursorVel.x = 0;
|
|
} else {
|
|
cursorVel.x += accX * (cursorVel.x > 0 ? -1 : 1);
|
|
}
|
|
if (Math.round(cursorVel.y * 1000) == 0) {
|
|
cursorVel.y = 0;
|
|
} else {
|
|
cursorVel.y += accY * (cursorVel.y > 0 ? -1 : 1);
|
|
}
|
|
}
|
|
|
|
function updateFan() {
|
|
// update fan position
|
|
fanElem.style.left = `${fanPos.x}px`;
|
|
fanElem.style.top = `${fanPos.y}px`;
|
|
// update fan rotation
|
|
const diffX = (cursorPos.x + cursorElem.clientWidth/2) - (fanPos.x + fanElem.clientWidth/2);
|
|
const diffY = (cursorPos.y + cursorElem.clientHeight/2) - (fanPos.y + fanElem.clientHeight/2);
|
|
let rotation = Math.atan(diffY / diffX) * 180 / Math.PI;
|
|
if(diffX < 0) rotation += 180;
|
|
if(diffY < 0) rotation += 360;
|
|
fanElem.style.transform = `rotate(${rotation}deg)`;
|
|
}
|
|
|
|
function updateCursor(deltaTime) {
|
|
cursorPos.x += cursorVel.x * deltaTime;
|
|
cursorPos.y += cursorVel.y * deltaTime;
|
|
if (cursorPos.x < 0) {
|
|
cursorPos.x = 0;
|
|
} else if (cursorPos.x > document.body.clientWidth - cursorElem.clientWidth) {
|
|
cursorPos.x = document.body.clientWidth - cursorElem.clientWidth;
|
|
}
|
|
if (cursorPos.y < 0) {
|
|
cursorPos.y = 0;
|
|
} else if (cursorPos.y > document.body.clientHeight - cursorElem.clientHeight) {
|
|
cursorPos.y = document.body.clientHeight - cursorElem.clientHeight;
|
|
}
|
|
cursorElem.style.left = `${cursorPos.x}px`;
|
|
cursorElem.style.top = `${cursorPos.y}px`;
|
|
}
|
|
|
|
function isCollision(rect1, rect2) {
|
|
return (rect1.top < rect2.bottom && rect1.bottom > rect2.top &&
|
|
rect1.left < rect2.right && rect1.right > rect2.left);
|
|
}
|
|
|
|
let lastKey = null;
|
|
function handleCursorHover() {
|
|
for (const key of keyElems) {
|
|
if (isCollision(cursorElem.getBoundingClientRect(), key.getBoundingClientRect())) {
|
|
if (key === lastKey) {
|
|
return;
|
|
}
|
|
lastKey = key;
|
|
if (key.id === "clear-btn") {
|
|
textElem.innerText = "";
|
|
} else {
|
|
textElem.innerText += key.dataset.key;
|
|
}
|
|
key.classList.add("hover");
|
|
return;
|
|
}
|
|
}
|
|
lastKey?.classList.remove("hover");
|
|
lastKey = null;
|
|
}
|
|
|
|
let lastTime = null;
|
|
function update(time) {
|
|
requestAnimationFrame(update);
|
|
if (!lastTime) {
|
|
lastTime = time;
|
|
return;
|
|
}
|
|
const deltaTime = time - lastTime;
|
|
lastTime = time;
|
|
|
|
blowCursor(deltaTime);
|
|
decelerateCursor(deltaTime);
|
|
updateFan();
|
|
updateCursor(deltaTime);
|
|
handleCursorHover();
|
|
}
|
|
|
|
requestAnimationFrame(update);
|