Docs
Mouse Image Trail
A mouse image trail is an effect where an image follows the cursor, leaving a dynamic trail that fades out smoothly, creating an interactive and engaging visual experience.
Installation
Install dependencies
npm install framer-motion lucide-react
Update tailwind.config.js
Add the following to your tailwind.config.js file.
module.exports = {
theme: {
extend: {
}
}
}
Run the following command
It will create a new file mouse-image-trail.tsx
inside the components/mage-ui/image
directory.
mkdir -p components/mage-ui/image && touch components/mage-ui/image/mouse-image-trail.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import { useAnimate } from "framer-motion";
import React, { MouseEventHandler, ReactNode, useRef } from "react";
import { FiMousePointer } from "react-icons/fi";
export const ImageTrail = () => {
return (
<MouseImageTrail
renderImageBuffer={50}
rotationRange={25}
images={[
"https://images.unsplash.com/photo-1635373670332-43ea883bb081?q=80&w=2781&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1576174464184-fb78fe882bfd?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1503751071777-d2918b21bbd9?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1620428268482-cf1851a36764?q=80&w=2609&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1602212096437-d0af1ce0553e?q=80&w=2671&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1622313762347-3c09fe5f2719?q=80&w=2640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
]}
>
<section className="grid h-screen w-[90vw] place-content-center bg-white">
<p className="flex items-center gap-2 text-3xl font-bold uppercase text-black">
<FiMousePointer />
<span>Hover me</span>
</p>
</section>
</MouseImageTrail>
);
};
const MouseImageTrail = ({
children,
// List of image sources
images,
// Will render a new image every X pixels between mouse moves
renderImageBuffer,
// images will be rotated at a random number between zero and rotationRange,
// alternating between a positive and negative rotation
rotationRange,
}: {
children: ReactNode;
images: string[];
renderImageBuffer: number;
rotationRange: number;
}) => {
const [scope, animate] = useAnimate();
const lastRenderPosition = useRef({ x: 0, y: 0 });
const imageRenderCount = useRef(0);
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
const { clientX, clientY } = e;
const distance = calculateDistance(
clientX,
clientY,
lastRenderPosition.current.x,
lastRenderPosition.current.y
);
if (distance >= renderImageBuffer) {
lastRenderPosition.current.x = clientX;
lastRenderPosition.current.y = clientY;
renderNextImage();
}
};
const calculateDistance = (
x1: number,
y1: number,
x2: number,
y2: number
) => {
const deltaX = x2 - x1;
const deltaY = y2 - y1;
// Using the Pythagorean theorem to calculate the distance
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
return distance;
};
const renderNextImage = () => {
const imageIndex = imageRenderCount.current % images.length;
const selector = `[data-mouse-move-index="${imageIndex}"]`;
const el = document.querySelector(selector) as HTMLElement;
el.style.top = `${lastRenderPosition.current.y}px`;
el.style.left = `${lastRenderPosition.current.x}px`;
el.style.zIndex = imageRenderCount.current.toString();
const rotation = Math.random() * rotationRange;
animate(
selector,
{
opacity: [0, 1],
transform: [
`translate(-50%, -25%) scale(0.5) ${imageIndex % 2
? `rotate(${rotation}deg)`
: `rotate(-${rotation}deg)`
}`,
`translate(-50%, -50%) scale(1) ${imageIndex % 2
? `rotate(-${rotation}deg)`
: `rotate(${rotation}deg)`
}`,
],
},
{ type: "spring", damping: 15, stiffness: 200 }
);
animate(
selector,
{
opacity: [1, 0],
},
{ ease: "linear", duration: 0.5, delay: 5 }
);
imageRenderCount.current = imageRenderCount.current + 1;
};
return (
<div
ref={scope}
className="relative overflow-hidden"
onMouseMove={handleMouseMove}
>
{children}
{images.map((img, index) => (
<img
className="pointer-events-none absolute left-0 top-0 h-48 w-auto rounded-xl border-2 border-black bg-neutral-900 object-cover opacity-0"
src={img}
alt={`Mouse move image ${index}`}
key={index}
data-mouse-move-index={index}
/>
))}
</div>
);
};