first commit
This commit is contained in:
Executable
+58
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Fan controlled cursor</title>
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<script defer src="./main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="keyboard-wrapper">
|
||||
<div id="text"></div>
|
||||
<div id="keyboard">
|
||||
<div class="key" data-key="A">A</div>
|
||||
<div class="key" data-key="B">B</div>
|
||||
<div class="key" data-key="C">C</div>
|
||||
<div class="key" data-key="D">D</div>
|
||||
<div class="key" data-key="E">E</div>
|
||||
<div class="key" data-key="F">F</div>
|
||||
<div class="key" data-key="G">G</div>
|
||||
<div class="key" data-key="H">H</div>
|
||||
<div class="key" data-key="I">I</div>
|
||||
<div class="key" data-key="J">J</div>
|
||||
<div class="key" data-key="K">K</div>
|
||||
<div class="key" data-key="L">L</div>
|
||||
<div class="key" data-key="M">M</div>
|
||||
<div class="key" data-key="N">N</div>
|
||||
<div class="key" data-key="O">O</div>
|
||||
<div class="key" data-key="P">P</div>
|
||||
<div class="key" data-key="Q">Q</div>
|
||||
<div class="key" data-key="R">R</div>
|
||||
<div class="key" data-key="S">S</div>
|
||||
<div class="key" data-key="T">T</div>
|
||||
<div class="key" data-key="U">U</div>
|
||||
<div class="key" data-key="V">V</div>
|
||||
<div class="key" data-key="W">W</div>
|
||||
<div class="key" data-key="X">X</div>
|
||||
<div class="key" data-key="Y">Y</div>
|
||||
<div class="key" data-key="Z">Z</div>
|
||||
<div class="key" data-key="0">0</div>
|
||||
<div class="key" data-key="1">1</div>
|
||||
<div class="key" data-key="2">2</div>
|
||||
<div class="key" data-key="3">3</div>
|
||||
<div class="key" data-key="4">4</div>
|
||||
<div class="key" data-key="5">5</div>
|
||||
<div class="key" data-key="6">6</div>
|
||||
<div class="key" data-key="7">7</div>
|
||||
<div class="key" data-key="8">8</div>
|
||||
<div class="key" data-key="9">9</div>
|
||||
<div class="key wide" data-key=" ">Space</div>
|
||||
<div class="key wide" id="clear-btn">Clear</div>
|
||||
</div>
|
||||
</div>
|
||||
<img id="cursor" src="./assets/cursor.png" draggable="false">
|
||||
<img id="fan" src="./assets/fan.png" draggable="false">
|
||||
</body>
|
||||
</html>
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
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);
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
}
|
||||
|
||||
#fan {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 6rem;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
#fan:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
#cursor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.keyboard-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 71rem;
|
||||
}
|
||||
|
||||
#text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 56rem;
|
||||
height: 3rem;
|
||||
font-size: 2rem;
|
||||
background-color: gray;
|
||||
color: white;
|
||||
padding: 0 0.6rem;
|
||||
margin: 2.2rem 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#cursor, #fan, #keyboard {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.key {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
margin: 2.2rem;
|
||||
background-color: lightgray;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.key.hover {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.key.wide {
|
||||
width: 9.5rem;
|
||||
}
|
||||
Reference in New Issue
Block a user