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>