Image Mousetrail

An image with a mouse trail effect, where the image responds dynamically to cursor movement, creating a visually engaging interaction.

image-0image-1image-2image-3image-4image-5image-6image-7image-8

✨ Experience Interactive Designs with
Dynamic Mouse Trails

Usage

mousetrail.tsx
1
//@ts-nocheck
2
'use client';
3
import { cn } from '@/lib/utils';
4
import { createRef, useRef } from 'react';
5
interface ImageMouseTrailProps {
6
items: any[];
7
children?: ReactNode;
8
className?: string;
9
imgClass?: string;
10
distance?: number;
11
maxNumberOfImages?: number;
12
fadeAnimation?: boolean;
13
}
14
export default function ImageMouseTrail({
15
items,
16
children,
17
className,
18
maxNumberOfImages = 5,
19
imgClass = 'w-40 h-48',
20
distance = 20,
21
fadeAnimation = false,
22
}: ImageMouseTrailProps) {
23
const containerRef = useRef(null);
24
const refs = useRef(items.map(() => createRef<HTMLImageElement>()));
25
const currentZIndexRef = useRef(1);
26
27
let globalIndex = 0;
28
let last = { x: 0, y: 0 };
29
30
const activate = (image, x, y) => {
31
const containerRect = containerRef.current?.getBoundingClientRect();
32
const relativeX = x - containerRect.left;
33
const relativeY = y - containerRect.top;
34
image.style.left = `${relativeX}px`;
35
image.style.top = `${relativeY}px`;
36
console.log(refs.current[refs.current?.length - 1]);
37
38
if (currentZIndexRef.current > 40) {
39
currentZIndexRef.current = 1;
40
}
41
image.style.zIndex = String(currentZIndexRef.current);
42
currentZIndexRef.current++;
43
44
image.dataset.status = 'active';
45
if (fadeAnimation) {
46
setTimeout(() => {
47
image.dataset.status = 'inactive';
48
}, 1500);
49
}
50
last = { x, y };
51
};
52
53
const distanceFromLast = (x, y) => {
54
return Math.hypot(x - last.x, y - last.y);
55
};
56
const deactivate = (image) => {
57
image.dataset.status = 'inactive';
58
};
59
60
const handleOnMove = (e) => {
61
if (distanceFromLast(e.clientX, e.clientY) > window.innerWidth / distance) {
62
console.log(e.clientX, e.clientY);
63
64
const lead = refs.current[globalIndex % refs.current.length].current;
65
66
const tail =
67
refs.current[(globalIndex - maxNumberOfImages) % refs.current.length]
68
?.current;
69
70
if (lead) activate(lead, e.clientX, e.clientY);
71
if (tail) deactivate(tail);
72
globalIndex++;
73
}
74
};
75
76
return (
77
<section
78
onMouseMove={handleOnMove}
79
onTouchMove={(e) => handleOnMove(e.touches[0])}
80
ref={containerRef}
81
className={cn(
82
'grid place-content-center h-[600px] w-full bg-[#e0dfdf] relative overflow-hidden rounded-lg',
83
className
84
)}
85
>
86
{items.map((item, index) => (
87
<>
88
<img
89
key={index}
90
className={cn(
91
"object-cover scale-0 opacity:0 data-[status='active']:scale-100 data-[status='active']:opacity-100 transition-transform data-[status='active']:duration-500 duration-300 data-[status='active']:ease-out-expo absolute -translate-y-[50%] -translate-x-[50%] ",
92
imgClass
93
)}
94
data-index={index}
95
data-status='inactive'
96
src={item}
97
alt={`image-${index}`}
98
ref={refs.current[index]}
99
/>
100
</>
101
))}
102
<article className='relative z-50 mix-blend-difference'>
103
{children}
104
</article>
105
</section>
106
);
107
}

I use data-status atribute to handle active and inactive item and you can add animation using this attribute:

{
items.map((item, index) => (
<>
<img
key={index}
className={cn(
"object-cover scale-0 opacity:0 data-[status='active']:scale-100 data-[status='active']:opacity-100 transition-transform data-[status='active']:duration-500 duration-300 data-[status='active']:ease-out-expo absolute -translate-y-[50%] -translate-x-[50%] ",
imgClass
)}
data-index={index}
data-status='inactive'
src={item}
alt={`image-${index}`}
ref={refs.current[index]}
/>
</>
));
}

Add the following cubic-bazeir to your tailwind.config.js file:

transitionTimingFunction: {
'out-expo': 'cubic-bezier(0.34, 1.56, 0.64, 1)',
},

you can fade out animation when you don't mousemove:

const activate = (image, x, y) => {
setTimeout(() => {
image.dataset.status = 'inactive';
}, 1000);
};

Props

PropTypeDefaultDescription
itemsany[]-Array of items to display in the mouse trail effect.
childrenReactNodeundefinedOptional child elements to render inside the component.
classNamestringundefinedOptional CSS class for styling the wrapper of the component.
imgClassstring'w-40 h-48'CSS class for styling the images in the mouse trail.
distancenumber20Distance between each image in the mouse trail.
maxNumberOfImagesnumber5Maximum number of images to display in the trail.
fadeAnimationbooleanfalseWhether to apply fade animation to the images.

Example

Small Images

image-0image-1image-2image-3image-4image-5image-6image-7image-8image-9image-10image-11image-12

✨Experience

Disappear Images

image-0image-1image-2image-3image-4image-5image-6image-7image-8

✨ Experience Interactive Designs with
Dynamic Mouse Trails

Without Components

image-0image-1image-2image-3image-4image-5image-6image-7image-8image-9image-10image-11image-12image-13image-14image-15image-16image-17

✨ Experience Interactive Designs
with Dynamic Mouse Trails
built with Tailwind CSS