I’m trying to create multiple image trails for a grid. Each trail follow the cursor, I found an example on Codepen to illustrate the effect I’m looking for. The example is made with GSAP, but for this project I would prefer not using any libraries.
I’ve success to make the block of images following the cursor but I don’t find the way to reproduce the effect in Javascript.
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(2, 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;
width: 150px;
height: 200px;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: -1;
opacity: 0;
transition: opacity .3s ease .1s;
}
.grid__item-cursor.is-visible{
z-index: 1;
opacity: 1;
}
.grid__item-image{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
<div class="grid">
<div class="grid__item js-post">
<div class="grid__item-number">1</div>
<div class="grid__item-cursor js-cursor">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2022/11/13/18/09/canyon-7589820_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2022/11/02/22/33/autumn-7566201_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2023/04/05/09/44/landscape-7901065_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2020/09/04/16/18/mountains-5544365_1280.jpg">
</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">2</div>
<div class="grid__item-cursor js-cursor">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2022/11/13/18/09/canyon-7589820_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2022/11/02/22/33/autumn-7566201_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2023/04/05/09/44/landscape-7901065_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2020/09/04/16/18/mountains-5544365_1280.jpg">
</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">3</div>
<div class="grid__item-cursor js-cursor">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2022/11/13/18/09/canyon-7589820_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2022/11/02/22/33/autumn-7566201_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2023/04/05/09/44/landscape-7901065_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2020/09/04/16/18/mountains-5544365_1280.jpg">
</div>
</div>
<div class="grid__item js-post">
<div class="grid__item-number">4</div>
<div class="grid__item-cursor js-cursor">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2022/11/13/18/09/canyon-7589820_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2022/11/02/22/33/autumn-7566201_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2023/04/05/09/44/landscape-7901065_1280.jpg">
<img class="grid__item-image js-image" src="https://cdn.pixabay.com/photo/2020/09/04/16/18/mountains-5544365_1280.jpg">
</div>
</div>
</div>