project demo
Introduction:
Hey Developers! In this project, we are creating a fun Login Form that reacts to the user’s mouse. The idea is to make a button that tilts and runs away when the form inputs are empty. This small project will help us understand how HTML, CSS, and JavaScript work together. We will learn how to add error messages, 3D tilt effects, and a repel animation.
The goal is to make a simple login form feel interactive and a little playful. By the end, we will understand how mouse movement can control elements on a web page.
SOURCE CODE 👉 Scroll down and download zip file of this project.
HTML:
Inside the body, we created a container that centers the form on the page. The form includes two input fields for email and password so we can check if they are empty. We placed hidden <span> error messages below each input to show warnings when the fields are blank. A special button container is used so the login button can move freely inside it. The login button itself is given an ID so JavaScript can tilt it and make it run away. At the end, we linked the JavaScript file to apply the repel and tilt logic on the button.
.
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Repel + 3D Tilt Button Form</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<form id="loginForm">
<h2>Login</h2>
<input type="email" id="email" placeholder="Email Address">
<span id="emailError" class="error-msg">Please enter your email.</span>
<input type="password" id="password" placeholder="Password">
<span id="passwordError" class="error-msg">Please enter your password.</span>
<div class="btn-container" id="btnContainer">
<button id="tilt">Login</button>
</div>
</form>
</div>
<script src="script.js"></script>
</body>
</html>
.CSS:
The CSS starts by importing a clean and modern Google font that makes the form look stylish. Then we reset basic margins and paddings to keep the layout neat. The body is styled with a gradient background and centered contents using flexbox. We also apply a perspective value to enable smooth 3D tilt effects. The container gets a glass effect using backdrop-filter and soft shadows, giving it a modern UI feel. Inputs are styled with transparent backgrounds, glowing borders, and error color highlights. The button is designed with shadows, rounded corners, and 3D properties to support tilt animation. Overall, the CSS gives the whole interface a smooth and premium look.
.
.
@import url('https://fonts.googleapis.com/css2?family=Stack+Sans+Text:wght@200..700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: "Stack Sans Text", sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
perspective: 1000px;
overflow: hidden;
}
.container {
border: 2px solid rgba(255, 255, 255, 0.2);
width: 400px;
padding: 0 20px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
}
form {
width: 100%;
padding: 30px 20px;
display: flex;
flex-direction: column;
align-items: center;
}
form h2 {
text-transform: uppercase;
letter-spacing: 3px;
margin-bottom: 10px;
color: white;
font-weight: 600;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
input {
width: 100%;
padding: 15px 20px;
border-radius: 8px;
margin: 15px 0 5px 0;
border: 1px solid rgba(255, 255, 255, 0.3);
outline: none;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 14px;
transition: all 0.3s ease;
}
input::placeholder {
color: #fff;
}
input.error {
border-color: rgb(255, 213, 0) !important;
}
.error-msg {
font-size: 14px;
color: rgb(255, 213, 0);
margin-bottom: 10px;
display: none;
text-align: left;
}
input:focus {
border-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.15);
}
.btn-container {
width: 100%;
height: 70px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
button {
position: absolute;
border: none;
background: #667eea;
color: white;
font-size: 15px;
font-weight: 600;
padding: 12px 20px;
border-radius: 7px;
cursor: pointer;
backface-visibility: hidden;
transform-style: preserve-3d;
box-shadow:
0 10px 20px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.3),
inset 0 -5px 5px rgba(0, 0, 0, 0.1);
}
.
.JavaScript:
The JavaScript begins by selecting all important elements like the button, container, input fields, and error messages. We calculate the center position of the button so we can move it correctly inside the container. A function updates the button’s position based on these coordinates. Another function shows or hides the error messages depending on whether the fields are empty. On every mouse move inside the container, we calculate the distance between the cursor and the button. If the fields are empty and the mouse gets too close, the button automatically repels away using angle calculations. When the cursor is at a medium range, we apply a tilt effect using rotateX and rotateY values. If the user fills both fields, all effects turn off and errors are hidden. When the mouse leaves the area, the button resets to the center. This combination of distance, rotation, and field-checking creates the full interactive effect.
.
.
const el = document.getElementById("tilt");
const btnContainer = document.getElementById("btnContainer");
const email = document.getElementById("email");
const password = document.getElementById("password");
const emailError = document.getElementById("emailError");
const passwordError = document.getElementById("passwordError");
let btnX = btnContainer.clientWidth / 2;
let btnY = btnContainer.clientHeight / 2;
const height = el.clientHeight;
const width = el.clientWidth;
function updateButtonPosition() {
el.style.left = `${btnX - el.offsetWidth / 2}px`;
el.style.top = `${btnY - el.offsetHeight / 2}px`;
}
updateButtonPosition();
// Function to toggle errors based on repel/tilt activation
function showErrors() {
const emailEmpty = !email.value.trim();
const passEmpty = !password.value.trim();
if (emailEmpty) {
emailError.style.display = "block";
email.classList.add("error");
} else {
emailError.style.display = "none";
email.classList.remove("error");
}
if (passEmpty) {
passwordError.style.display = "block";
password.classList.add("error");
} else {
passwordError.style.display = "none";
password.classList.remove("error");
}
}
function hideErrors() {
emailError.style.display = "none";
passwordError.style.display = "none";
email.classList.remove("error");
password.classList.remove("error");
}
btnContainer.addEventListener("mousemove", (e) => {
const rect = btnContainer.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const dx = mouseX - btnX;
const dy = mouseY - btnY;
const distance = Math.sqrt(dx * dx + dy * dy);
const filled = email.value.trim() && password.value.trim();
// Only show messages + borders when empty
if (!filled) showErrors();
else hideErrors();
// REPEL MODE
if (!filled && distance < 80) {
const angle = Math.atan2(dy, dx);
btnX += Math.cos(angle) * -40;
btnY += Math.sin(angle) * -40;
const halfW = el.offsetWidth / 2;
const halfH = el.offsetHeight / 2;
btnX = Math.max(halfW, Math.min(btnContainer.clientWidth - halfW, btnX));
btnY = Math.max(halfH, Math.min(btnContainer.clientHeight - halfH, btnY));
updateButtonPosition();
}
// TILT MODE: distance 20-60
if (!filled && distance < 60 && distance > 20) {
const yRotation = 60 * (dx / width);
const xRotation = -60 * (dy / height);
el.style.transform = `perspective(500px) rotateX(${xRotation}deg) rotateY(${yRotation}deg)`;
} else {
el.style.transform = "perspective(500px) rotateX(0) rotateY(0)";
}
});
btnContainer.addEventListener("mouseleave", () => {
el.style.transform = "perspective(500px) rotateX(0) rotateY(0)";
hideErrors();
resetButtonPosition();
});
function resetButtonPosition() {
btnX = btnContainer.clientWidth / 2;
btnY = btnContainer.clientHeight / 2;
updateButtonPosition();
}
.DOWNLOAD CODE 👇
Download “Repel-Login-Form.zip” Repel-Login-Form.zip – Downloaded 25 times – 2.90 KB