I have a grid with multiple items. Each item has its own custom cursor with the div .grid__item-cursor
.
What I'm trying to achieve is when hovering an item, its cursor follows the mouse with a smooth speed.
The issue is when hovering an item, all the cursors from the document move at the same time. And there is also too much distance between the custom cursor and the mouse.
const cursors = document.querySelectorAll(".js-cursor");
let aimX = 0;
let aimY = 0;
cursors.forEach((cursor) => {
let currentX = 0;
let currentY = 0;
let speed = 0.2;
const animate = function () {
currentX += (aimX - currentX) * speed;
currentY += (aimY - currentY) * speed;
cursor.style.left = currentX + "px";
cursor.style.top = currentY + "px";
requestAnimationFrame(animate);
};
animate();
});
const posts = document.querySelectorAll(".js-post");
posts.forEach((post) => {
const cursor = post.querySelector(".js-cursor");
post.addEventListener("mousemove", function (event) {
aimX = event.pageX;
aimY = event.pageY;
});
post.addEventListener("mouseenter", function () {
cursor.classList.add("is-visible");
});
post.addEventListener("mouseleave", function () {
cursor.classList.remove("is-visible");
});
});
body {
font-family: "helvetica", arial, sans-serif;
}
.grid {
display: grid;
width: 100%;
grid-template-columns: repeat(4, 1fr);
grid-column-gap: 1rem;
grid-row-gap: 1rem;
}
.grid__item {
display: flex;
justify-content: center;
align-content: center;
position: relative;
padding: 25%;
overflow: hidden;
background-color: #333;
}
.grid__item-number {
color: #888;
font-size: 5rem;
}
.grid__item-cursor {
position: absolute;
top: 0;
left: 0;
padding: 0.25em;
background-color: red;
}
.grid__item-cursor.is-visible {
background-color: yellow;
}
<div class="grid">
<div class="grid__item js-post">
<div class="grid__item-number">1</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">2</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">3</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">4</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">5</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">6</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">7</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">8</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
</div>
I have a grid with multiple items. Each item has its own custom cursor with the div .grid__item-cursor
.
What I'm trying to achieve is when hovering an item, its cursor follows the mouse with a smooth speed.
The issue is when hovering an item, all the cursors from the document move at the same time. And there is also too much distance between the custom cursor and the mouse.
const cursors = document.querySelectorAll(".js-cursor");
let aimX = 0;
let aimY = 0;
cursors.forEach((cursor) => {
let currentX = 0;
let currentY = 0;
let speed = 0.2;
const animate = function () {
currentX += (aimX - currentX) * speed;
currentY += (aimY - currentY) * speed;
cursor.style.left = currentX + "px";
cursor.style.top = currentY + "px";
requestAnimationFrame(animate);
};
animate();
});
const posts = document.querySelectorAll(".js-post");
posts.forEach((post) => {
const cursor = post.querySelector(".js-cursor");
post.addEventListener("mousemove", function (event) {
aimX = event.pageX;
aimY = event.pageY;
});
post.addEventListener("mouseenter", function () {
cursor.classList.add("is-visible");
});
post.addEventListener("mouseleave", function () {
cursor.classList.remove("is-visible");
});
});
body {
font-family: "helvetica", arial, sans-serif;
}
.grid {
display: grid;
width: 100%;
grid-template-columns: repeat(4, 1fr);
grid-column-gap: 1rem;
grid-row-gap: 1rem;
}
.grid__item {
display: flex;
justify-content: center;
align-content: center;
position: relative;
padding: 25%;
overflow: hidden;
background-color: #333;
}
.grid__item-number {
color: #888;
font-size: 5rem;
}
.grid__item-cursor {
position: absolute;
top: 0;
left: 0;
padding: 0.25em;
background-color: red;
}
.grid__item-cursor.is-visible {
background-color: yellow;
}
<div class="grid">
<div class="grid__item js-post">
<div class="grid__item-number">1</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">2</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">3</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">4</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">5</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">6</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">7</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">8</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
</div>
Share
Improve this question
edited 12 hours ago
Mathieu Préaud
asked 12 hours ago
Mathieu PréaudMathieu Préaud
4111 gold badge4 silver badges18 bronze badges
1
|
1 Answer
Reset to default 1Here is a version that only follows the mouse in the grid element. When the mouse leaves the element, the position of the read READ ME is reset to 0,0 of the grid element. I wanted to delegate but the event bubbling made that non-trivial
const posts = document.querySelectorAll('.js-post');
let activePost = null;
let activeCursor = null;
let currentX = 0, currentY = 0;
let aimX = 0, aimY = 0;
const speed = 0.2;
const animate = () => {
if (activeCursor) {
currentX += (aimX - currentX) * speed;
currentY += (aimY - currentY) * speed;
activeCursor.style.left = currentX + 'px';
activeCursor.style.top = currentY + 'px';
}
requestAnimationFrame(animate);
};
animate();
posts.forEach(post => {
post.addEventListener('mouseenter', (e) => {
// Hide the previous grid element's cursor immediately, if any.
if (activePost && activePost !== post && activeCursor) {
activeCursor.classList.remove('is-visible');
// Reset the previous cursor to 0,0 relative to its container.
activeCursor.style.left = '0px';
activeCursor.style.top = '0px';
}
activePost = post;
activeCursor = post.querySelector('.js-cursor');
// Get grid item's bounding rectangle for local coordinate conversion.
const rect = post.getBoundingClientRect();
currentX = e.clientX - rect.left;
currentY = e.clientY - rect.top;
aimX = currentX;
aimY = currentY;
// Position the cursor immediately at the mouse's location.
activeCursor.style.left = currentX + 'px';
activeCursor.style.top = currentY + 'px';
activeCursor.classList.add('is-visible');
});
post.addEventListener('mousemove', (e) => {
if (activePost === post && activeCursor) {
const rect = post.getBoundingClientRect();
aimX = e.clientX - rect.left;
aimY = e.clientY - rect.top;
}
});
post.addEventListener('mouseleave', () => {
if (activePost === post && activeCursor) {
activeCursor.classList.remove('is-visible');
// Reset the coordinates to the top-left (0,0) of the grid element.
activeCursor.style.left = '0px';
activeCursor.style.top = '0px';
// Also reset the internal coordinates so the next activation starts from 0,0.
currentX = 0;
currentY = 0;
aimX = 0;
aimY = 0;
activePost = null;
activeCursor = null;
}
});
});
body {
font-family: "helvetica", arial, sans-serif;
}
.grid {
display: grid;
width: 100%;
grid-template-columns: repeat(4, 1fr);
grid-column-gap: 1rem;
grid-row-gap: 1rem;
}
.grid__item {
display: flex;
justify-content: center;
align-content: center;
position: relative;
padding: 25%;
background-color: #333;
}
.grid__item-number {
color: #888;
font-size: 5rem;
}
.grid__item-cursor {
position: absolute;
top: 0;
left: 0;
padding: 0.25em;
background-color: red;
}
.grid__item-cursor.is-visible {
background-color: yellow;
}
<div class="grid">
<div class="grid__item js-post">
<div class="grid__item-number">1</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">2</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">3</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">4</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">5</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">6</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">7</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">8</div>
<div class="grid__item-cursor js-cursor">Read more</div>
</div>
</div>
animate
function running in an interval, for all of them, all the time. – C3roe Commented 11 hours ago