const windowW = window.innerWidth;
const windowH = window.innerHeight;
const maxLength = Math.max(windowW, windowH);
const cursorWidth = 100;
const cursorR = cursorWidth >> 1;
const cursorDelay = 10;
const buttons = Array.from(document.querySelectorAll('.border-button'));
const cursor = {
el: document.querySelector('.border-cursor'),
x: windowW >> 1,
y: windowH >> 1,
scaleX: 1,
scaleY: 1,
};
const target = {
x: windowW >> 1,
y: windowH >> 1,
width: cursorWidth,
followMouse: true,
};
const norm = (val, max, min) => (val - min) / (max - min);
const toDegrees = r => r * (180 / Math.PI);
const distanceBetween = (v1, v2) => Math.sqrt((v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y +- v2.y));
const loop = () => {
const destX = target.x - cursorR;
const destY = target.y - cursorR;
const newX = cursor.x + ((destX - cursor.x) / cursorDelay);
const newY = cursor.y + ((destY - cursor.y) / cursorDelay);
const angle = angleBetween(cursor.x, cursor.y, newX, newY);
if (target.followMouse) {
const distance = Math.abs(distanceBetween(target, cursor));
const scale = norm(distance, maxLength, cursorR);
cursor.scaleX = 1 + scale;
cursor.scaleY = 1 - scale;
} else {
const targetScale = target.width / cursorWidth;
cursor.scaleX += (targetScale - cursor.scaleX) / (cursorDelay / 2);
cursor.scaleY = cursor.scaleX;
}
cursor.x = newX;
cursor.y = newY;
cursor.el.style.transform = `translate(${cursor.x}px, ${cursor.y}px) rotate(${toDegrees(angle)}deg) scale(${cursor.scaleX}, ${cursor.scaleY})`;
requestAnimationFrame(loop);
};
const angleBetween = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1);
const onPointerMove = (e) => {
if (!target.followMouse) {
return;
}
const pointer = (e.touches && e.touches.length) ? e.touches[0] : e;
const { clientX: x, clientY: y } = pointer;
target.x = x;
target.y = y;
};
const onPointerOver = (e) => {
const btn = e.target;
const rect = btn.getBoundingClientRect();
target.followMouse = false;
target.x = rect.left + (rect.width >> 1);
target.y = rect.top + (rect.height >> 1);
target.width = Math.max(rect.width, rect.height) + 50;
};
const onPointerOut = () => {
target.followMouse = true;
target.width = cursorWidth;
};
document.body.addEventListener('mousemove', onPointerMove);
document.body.addEventListener('touchmove', onPointerMove);
buttons.forEach((btn) => {
btn.addEventListener('touchstart', onPointerOver);
btn.addEventListener('mouseover', onPointerOver);
btn.addEventListener('touchend', onPointerOut);
btn.addEventListener('mouseout', onPointerOut);
});
loop();
html,
body {
margin: 0;
padding: 0;
}
.wrapper {
width: 100vw;
min-height: 1500px;
display: flex;
flex-direction: row;
align-items: center;
}
.container {
width: 100%;
display: flex;
padding: 0 1rem;
}
.cursor {
position: absolute;
z-index: 10;
width: 100px;
height: 100px;
border: 2px solid #23bfa0;
border-radius: 50%;
pointer-events: none;
}
.button {
padding: 1rem;
background-color: #23bfa0;
border: none;
box-shadow: 0 0 7px 0px rgba(0, 0, 0, 0.2);
color: white;
font-size: 1.2rem;
cursor: pointer;
transition: box-shadow 0.1s ease-in, transform 0.1s ease-in;
&--small {
padding: 0.75rem;
font-size: 0.75rem;
}
&:hover {
transform: translate(0%, -2px);
box-shadow: 0px 4px 9px 2px rgba(0, 0, 0, 0.2)
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="cursor border-cursor"></div>
<div class="wrapper">
<div class="container">
<button class="button button--small border-button">small</button>
<button class="button border-button">hover me</button>
<button class="button border-button">hover me more</button>
</div>
</div>
</body>