Image Mousetrail
An image with a mouse trail effect, where the image responds dynamically to cursor movement, creating a visually engaging interaction.
An image with a mouse trail effect, where the image responds dynamically to cursor movement, creating a visually engaging interaction.
1//@ts-nocheck2'use client';3import { cn } from '@/lib/utils';4import { createRef, useRef } from 'react';5interface ImageMouseTrailProps {6items: any[];7children?: ReactNode;8className?: string;9imgClass?: string;10distance?: number;11maxNumberOfImages?: number;12fadeAnimation?: boolean;13}14export default function ImageMouseTrail({15items,16children,17className,18maxNumberOfImages = 5,19imgClass = 'w-40 h-48',20distance = 20,21fadeAnimation = false,22}: ImageMouseTrailProps) {23const containerRef = useRef(null);24const refs = useRef(items.map(() => createRef<HTMLImageElement>()));25const currentZIndexRef = useRef(1);2627let globalIndex = 0;28let last = { x: 0, y: 0 };2930const activate = (image, x, y) => {31const containerRect = containerRef.current?.getBoundingClientRect();32const relativeX = x - containerRect.left;33const relativeY = y - containerRect.top;34image.style.left = `${relativeX}px`;35image.style.top = `${relativeY}px`;36console.log(refs.current[refs.current?.length - 1]);3738if (currentZIndexRef.current > 40) {39currentZIndexRef.current = 1;40}41image.style.zIndex = String(currentZIndexRef.current);42currentZIndexRef.current++;4344image.dataset.status = 'active';45if (fadeAnimation) {46setTimeout(() => {47image.dataset.status = 'inactive';48}, 1500);49}50last = { x, y };51};5253const distanceFromLast = (x, y) => {54return Math.hypot(x - last.x, y - last.y);55};56const deactivate = (image) => {57image.dataset.status = 'inactive';58};5960const handleOnMove = (e) => {61if (distanceFromLast(e.clientX, e.clientY) > window.innerWidth / distance) {62console.log(e.clientX, e.clientY);6364const lead = refs.current[globalIndex % refs.current.length].current;6566const tail =67refs.current[(globalIndex - maxNumberOfImages) % refs.current.length]68?.current;6970if (lead) activate(lead, e.clientX, e.clientY);71if (tail) deactivate(tail);72globalIndex++;73}74};7576return (77<section78onMouseMove={handleOnMove}79onTouchMove={(e) => handleOnMove(e.touches[0])}80ref={containerRef}81className={cn(82'grid place-content-center h-[600px] w-full bg-[#e0dfdf] relative overflow-hidden rounded-lg',83className84)}85>86{items.map((item, index) => (87<>88<img89key={index}90className={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%] ",92imgClass93)}94data-index={index}95data-status='inactive'96src={item}97alt={`image-${index}`}98ref={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) => (<><imgkey={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);};
Prop | Type | Default | Description |
---|---|---|---|
items | any[] | - | Array of items to display in the mouse trail effect. |
children | ReactNode | undefined | Optional child elements to render inside the component. |
className | string | undefined | Optional CSS class for styling the wrapper of the component. |
imgClass | string | 'w-40 h-48' | CSS class for styling the images in the mouse trail. |
distance | number | 20 | Distance between each image in the mouse trail. |
maxNumberOfImages | number | 5 | Maximum number of images to display in the trail. |
fadeAnimation | boolean | false | Whether to apply fade animation to the images. |