Skip to content

AMARDEV07/911-UI-

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 

Repository files navigation

🎯 Image Cursor Trail Component - Complete Guide

📋 Table of Contents

🔍 Overview

Image Cursor Trail ek interactive React component hai jo mouse cursor ke behind images ka trail create karta hai. Ye effect sirf designated container ke andar hi active hota hai.

✨ Key Features:

  • Smooth cursor tracking - 60fps smooth movement
  • Random image selection - Array se random images pick karta hai
  • Boundary detection - Effect sirf container ke andar
  • Performance optimized - GPU acceleration aur efficient rendering
  • Mobile responsive - Touch devices pe bhi works
  • Customizable - Easy to modify aur extend

⚙️ How It Works

🎮 Basic Flow:

1. Mouse Move → Track Position
2. Inside Container? → Check Boundaries  
3. Add New Image → Random selection + positioning
4. Animate Trail → Fade out + drift effect
5. Cleanup → Remove old images

🧠 Core Logic:

// 1. Mouse position tracking (document level)
document.addEventListener('mousemove', handleMouseMove);

// 2. Boundary detection
const isInside = (mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom);

// 3. Image creation (throttled)
if (now - lastImageTime > 150ms) {
  createNewImage();
}

// 4. Animation loop (60fps)
setInterval(() => {
  updateTrailImages();
}, 60ms);

🏗️ Component Architecture

📁 File Structure:

components/
├── ImageCursorTrail.jsx     # Main component logic
├── ImageCursorTrail.css     # Styling + animations
└── CarsImagesCollection.jsx # Usage example

🧩 Component Breakdown:

1. ImageCursorTrail.jsx (Main Component)

const ImageCursorTrail = ({ items, maxNumberOfImages, distance, imgClass, children }) => {
  // State management
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
  const [isInsideContainer, setIsInsideContainer] = useState(false);
  const [imageTrail, setImageTrail] = useState([]);
  
  // Refs for performance
  const containerRef = useRef(null);
  const lastImageTime = useRef(0);
  const imageCounter = useRef(0);
  
  // Event handlers + lifecycle
  useEffect(() => { /* Mouse tracking */ }, []);
  useEffect(() => { /* Trail animation */ }, []);
  useEffect(() => { /* Cleanup */ }, []);
  
  return (
    <div ref={containerRef}>
      {/* Trail Images */}
      {/* Custom Cursor */}
      {/* Content */}
    </div>
  );
};

2. Props Interface:

interface Props {
  items: string[];           // Array of image URLs
  maxNumberOfImages?: number; // Max trail length (default: 5)
  distance?: number;         // Distance from cursor (default: 25px)
  imgClass?: string;         // CSS class for image size
  children: ReactNode;       // Content to display
}

📊 State Management

🎯 State Variables:

1. mousePosition

const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
  • Purpose: Real-time cursor coordinates
  • Update: Document-level mousemove event
  • Usage: Custom cursor positioning

2. isInsideContainer

const [isInsideContainer, setIsInsideContainer] = useState(false);
  • Purpose: Track if mouse is inside component boundary
  • Update: Boundary detection calculation
  • Usage: Conditional cursor visibility + trail creation

3. imageTrail

const [imageTrail, setImageTrail] = useState([]);
  • Structure:
[
  {
    id: 1,
    x: 500,
    y: 300,
    src: "image-url",
    opacity: 1,
    scale: 0.9,
    rotation: 15
  }
]
  • Purpose: Trail images data aur positioning
  • Update: Add new + animate existing + remove old

🔧 Refs (Performance):

1. containerRef

const containerRef = useRef(null);
  • Purpose: DOM reference for boundary calculations
  • Usage: getBoundingClientRect() for position detection

2. lastImageTime

const lastImageTime = useRef(0);
  • Purpose: Throttling image creation
  • Usage: Prevent too many images spawning rapidly

3. imageCounter

const imageCounter = useRef(0);
  • Purpose: Unique IDs for images
  • Usage: React key prop + z-index management

🎪 Event Handling

🖱️ Mouse Events:

1. Document MouseMove

const handleMouseMove = (e) => {
  // Always update position (smooth cursor)
  setMousePosition({ x: e.clientX, y: e.clientY });
  
  // Boundary detection
  const rect = containerRef.current.getBoundingClientRect();
  const isInside = (
    e.clientX >= rect.left && e.clientX <= rect.right &&
    e.clientY >= rect.top && e.clientY <= rect.bottom
  );
  
  setIsInsideContainer(isInside);
  
  // Create trail images (only when inside)
  if (isInside && shouldCreateImage()) {
    createNewImage(e.clientX, e.clientY);
  }
};

2. Image Creation Logic

const createNewImage = (x, y) => {
  const newImage = {
    id: imageCounter.current++,
    x: x,
    y: y,
    src: items[Math.floor(Math.random() * items.length)], // Random selection
    opacity: 1,
    scale: 0.8 + Math.random() * 0.4,  // Random size (0.8-1.2)
    rotation: (Math.random() - 0.5) * 30 // Random rotation (-15° to +15°)
  };
  
  setImageTrail(prev => [newImage, ...prev].slice(0, maxNumberOfImages));
};

3. Throttling Mechanism

const shouldCreateImage = () => {
  const now = Date.now();
  if (now - lastImageTime.current > 150) { // 150ms throttle
    lastImageTime.current = now;
    return true;
  }
  return false;
};

🎨 CSS Architecture

🏗️ CSS Structure:

1. Container Setup

.cursor-trail-container {
  position: relative;        /* Positioning context */
  min-height: 100vh;        /* Full viewport */
  overflow: hidden;         /* Prevent scrollbars */
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.cursor-trail-container:hover {
  cursor: none;             /* Hide default cursor on hover */
}

2. Trail Images

.trail-image {
  position: fixed;          /* Fixed positioning for smooth movement */
  pointer-events: none;     /* Don't interfere with mouse events */
  object-fit: cover;        /* Maintain aspect ratio */
  border-radius: 12px;      /* Rounded corners */
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); /* Depth */
  transition: opacity 0.2s ease-out, transform 0.1s ease-out;
  z-index: 40;              /* Layer management */
  will-change: transform;   /* GPU acceleration hint */
}

3. Custom Cursor

.custom-cursor {
  position: fixed;
  width: 20px;
  height: 20px;
  background: radial-gradient(circle, #ffffff 0%, #f0f0f0 70%, #e0e0e0 100%);
  border: 2px solid rgba(255, 255, 255, 0.8);
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;           /* Always on top */
  transform: translate(-50%, -50%); /* Center on cursor */
  transition: all 0.1s ease-out;
  will-change: transform;  /* Smooth movement */
}

🎭 Animation System:

1. Image Lifecycle Animation

// Creation
.trail-image {
  animation: imageAppear 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes imageAppear {
  0% { opacity: 0; transform: scale(0.5) rotate(-10deg); }
  50% { opacity: 0.8; transform: scale(1.1) rotate(5deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

// Fade out (JavaScript)
setInterval(() => {
  setImageTrail(prev => 
    prev.map((img, index) => ({
      ...img,
      opacity: Math.max(0, img.opacity - 0.025), // Gradual fade
      scale: img.scale * 0.985,                  // Slight shrink
      y: img.y + (index * 1.5)                  // Drift down
    })).filter(img => img.opacity > 0.1)        // Remove faded
  );
}, 60);

⚡ Performance Optimizations

🚀 Key Optimizations:

1. GPU Acceleration

.trail-image,
.custom-cursor {
  will-change: transform;  /* Hint browser for GPU layer */
  transform: translateZ(0); /* Force hardware acceleration */
}

2. Efficient Event Handling

// Document-level listener (not container-level)
document.addEventListener('mousemove', handleMouseMove);

// Throttling to prevent too many images
if (now - lastImageTime.current > 150) {
  createNewImage();
}

3. Memory Management

// Automatic cleanup
.filter(img => img.opacity > 0.1)  // Remove faded images

// Limit trail length
.slice(0, maxNumberOfImages)       // Cap array size

// Cleanup on unmount
return () => {
  document.removeEventListener('mousemove', handleMouseMove);
};

4. React Optimizations

// useRef for non-reactive values
const lastImageTime = useRef(0);    // No re-renders
const imageCounter = useRef(0);     // Persistent counter

// Conditional rendering
{isInsideContainer && <CustomCursor />}

🎛️ Customization Options

📝 Props Customization:

1. Image Settings

<ImageCursorTrail
  items={imageUrls}           // Array of image URLs
  maxNumberOfImages={8}       // More/less trail length
  distance={40}               // Further from cursor
  imgClass="large-size"       // Custom size class
/>

2. CSS Classes

/* Custom size classes */
.large-size { width: 200px; height: 250px; }
.small-size { width: 80px; height: 100px; }
.square-size { width: 150px; height: 150px; }

3. Animation Timing

// In component
const THROTTLE_TIME = 100;        // Faster image creation
const FADE_SPEED = 0.05;          // Faster fade out
const ANIMATION_INTERVAL = 30;    // Smoother animation (120fps)

4. Visual Effects

/* Different cursor styles */
.glow-cursor {
  box-shadow: 0 0 50px rgba(255, 255, 255, 0.8);
  background: radial-gradient(circle, #ff6b6b, #4ecdc4);
}

/* Different image effects */
.neon-images {
  box-shadow: 0 0 30px rgba(0, 255, 255, 0.5);
  border: 2px solid rgba(0, 255, 255, 0.3);
}

🔧 Troubleshooting

❌ Common Issues & Solutions:

1. Cursor Not Smooth

Problem: Laggy cursor movement Solution:

// Use document-level listener
document.addEventListener('mousemove', handler);

// Add GPU acceleration
.custom-cursor { will-change: transform; }

2. Images Not Appearing

Problem: No trail images Solution:

// Check image URLs
console.log('Images loaded:', items);

// Check boundary detection
console.log('Inside container:', isInsideContainer);

3. Effect Outside Container

Problem: Trail appears on other pages Solution:

// Proper cleanup
useEffect(() => {
  return () => {
    document.removeEventListener('mousemove', handleMouseMove);
    setImageTrail([]); // Clear trail
  };
}, []);

4. Performance Issues

Problem: Slow rendering, frame drops Solution:

// Reduce max images
maxNumberOfImages={3}

// Increase throttle time
if (now - lastImageTime.current > 200) { ... }

// Optimize CSS
.trail-image { will-change: transform; }

🎯 Advanced Usage Examples

🌟 Custom Implementations:

1. Different Image Categories

const carImages = ['car1.jpg', 'car2.jpg'];
const animalImages = ['cat.jpg', 'dog.jpg'];

<ImageCursorTrail 
  items={hoveredCategory === 'cars' ? carImages : animalImages}
/>

2. Dynamic Configuration

const [trailConfig, setTrailConfig] = useState({
  maxImages: 5,
  distance: 25,
  size: 'medium'
});

<ImageCursorTrail 
  maxNumberOfImages={trailConfig.maxImages}
  distance={trailConfig.distance}
  imgClass={`${trailConfig.size}-size`}
/>

3. Multiple Trail Areas

function MultiTrailPage() {
  return (
    <div>
      <ImageCursorTrail items={galleryImages}>
        <Gallery />
      </ImageCursorTrail>
      
      <RegularSection />
      
      <ImageCursorTrail items={portfolioImages}>
        <Portfolio />
      </ImageCursorTrail>
    </div>
  );
}

📚 Learning Path

🎓 Skills Needed:

  1. React Hooks - useState, useEffect, useRef
  2. JavaScript Events - Mouse events, event delegation
  3. CSS Animations - Transitions, keyframes
  4. Performance - GPU acceleration, throttling
  5. Math - Coordinate calculations, boundary detection

🛠️ Build Your Own:

  1. Start with basic mouse tracking
  2. Add image rendering
  3. Implement boundary detection
  4. Add animations and effects
  5. Optimize performance
  6. Add customization options

🔗 Useful Resources:


📄 Component Summary

ImageCursorTrail ek advanced interactive component hai jo:

Smooth cursor tracking provide karta hai
Boundary-aware effect sirf designated area main
Performance optimized GPU acceleration ke saath
Highly customizable props aur CSS se
Mobile responsive touch devices ke liye
Production ready proper cleanup aur error handling

Is guide ko follow kar ke aap similar advanced components bana sakte hain! 🚀

360° Product Viewer - Complete Step-by-Step Explanation

Step 1: Basic Setup and Imports

import React, { useState, useRef, useEffect } from 'react';

Explanation:

  • useState - For state management (like current image index, dragging status)
  • useRef - To access DOM elements directly
  • useEffect - To handle side effects (like autoplay, event listeners)

Step 2: State Variables Setup

const [currentIndex, setCurrentIndex] = useState(0);
const [isDragging, setIsDragging] = useState(false);
const [isAutoplay, setIsAutoplay] = useState(true);
const [lastMouseX, setLastMouseX] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const containerRef = useRef(null);
const autoplayRef = useRef(null);

Purpose of Each State:

  • currentIndex - Which image is currently showing (0 to 35)
  • isDragging - Whether user is dragging or not
  • isAutoplay - Whether automatic rotation is running
  • lastMouseX - Previous mouse position (to calculate drag distance)
  • isLoading - Whether image is loading
  • containerRef - Reference to the image container
  • autoplayRef - Reference to control the autoplay interval

Step 3: Configuration Variables

const totalImages = 36; // Total number of images
const autoplaySpeed = 400; // Speed in milliseconds

Explanation:

  • These variables can be easily modified
  • If you have 50 images, just change totalImages = 50

Step 4: Image URL Generator Function

const getImageUrl = (index) => {
  return `/Car/img_0_0_${index + 1}.jpg`;
};

Explanation:

  • This function returns the correct image path for any index
  • index + 1 because images start from 1, not 0
  • Example: for index 0, it returns /Car/img_0_0_1.jpg

Step 5: Autoplay Functionality

useEffect(() => {
  if (isAutoplay && !isDragging) {
    autoplayRef.current = setInterval(() => {
      setCurrentIndex((prev) => (prev + 1) % totalImages);
    }, autoplaySpeed);
  } else {
    clearInterval(autoplayRef.current);
  }

  return () => clearInterval(autoplayRef.current);
}, [isAutoplay, isDragging, autoplaySpeed, totalImages]);

Step-by-step breakdown:

  1. If autoplay is ON and user is not dragging
  2. Start a timer that runs every autoplaySpeed milliseconds
  3. In the timer, increment the currentIndex
  4. % totalImages ensures that after the last image, it goes back to the first
  5. If autoplay is OFF or user is dragging, stop the timer
  6. Clean up the timer when component unmounts

Step 6: Mouse Drag Handlers

Mouse Down Handler:

const handleMouseDown = (e) => {
  setIsDragging(true);
  setIsAutoplay(false);
  setLastMouseX(e.clientX);
};

Explanation:

  • When user presses mouse, start dragging
  • Pause autoplay
  • Store current mouse X position

Mouse Move Handler:

const handleMouseMove = (e) => {
  if (!isDragging) return;

  const deltaX = e.clientX - lastMouseX;
  const sensitivity = 5;

  if (Math.abs(deltaX) > sensitivity) {
    const direction = deltaX > 0 ? 1 : -1;
    setCurrentIndex((prev) => {
      let newIndex = prev + direction;
      if (newIndex < 0) newIndex = totalImages - 1;
      if (newIndex >= totalImages) newIndex = 0;
      return newIndex;
    });
    setLastMouseX(e.clientX);
  }
};

Step-by-step:

  1. If not dragging, do nothing
  2. Calculate mouse movement distance (deltaX)
  3. Only change image if movement is greater than sensitivity
  4. Right movement = next image, Left movement = previous image
  5. Handle edge cases (wrap around for first/last image)

Mouse Up Handler:

const handleMouseUp = () => {
  setIsDragging(false);
};

Explanation:

  • When mouse is released, stop dragging

Step 7: Touch Handlers (Mobile Support)

const handleTouchStart = (e) => {
  setIsDragging(true);
  setIsAutoplay(false);
  setLastMouseX(e.touches[0].clientX);
};

const handleTouchMove = (e) => {
  // Similar logic as handleMouseMove but uses touches[0].clientX
};

const handleTouchEnd = () => {
  setIsDragging(false);
};

Explanation:

  • Touch events are for mobile devices
  • Logic is same as mouse events, but uses touches[0].clientX instead of clientX

Step 8: Global Event Listeners

useEffect(() => {
  if (isDragging) {
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
    document.addEventListener('touchmove', handleTouchMove);
    document.addEventListener('touchend', handleTouchEnd);
  }

  return () => {
    // Remove all event listeners
  };
}, [isDragging, lastMouseX]);

Explanation:

  • Add event listeners to the document level
  • This allows dragging to work even when mouse moves outside the image container
  • Cleanup function removes all listeners to prevent memory leaks

Step 9: Main JSX Structure

Image Container:

<div 
  ref={containerRef}
  className={`viewer-container ${isDragging ? 'dragging' : ''}`}
  onMouseDown={handleMouseDown}
  onTouchStart={handleTouchStart}
>
  <img
    src={getImageUrl(currentIndex)}
    alt={`360° view frame ${currentIndex + 1}`}
    className="viewer-image"
    draggable={false}
    onLoad={() => setIsLoading(false)}
    onError={() => setIsLoading(false)}
  />
  
  {isLoading && (
    <div className="loading-overlay">
      <div className="loading-text">Loading...</div>
    </div>
  )}
</div>

Key Points:

  • getImageUrl(currentIndex) gets the current image path
  • draggable={false} disables browser's default drag behavior
  • Loading overlay only shows when isLoading is true
  • onLoad and onError both set loading to false

Control Buttons:

<div className="controls">
  <div className="nav-buttons">
    <button onClick={() => setCurrentIndex(prev => prev === 0 ? totalImages - 1 : prev - 1)}>
      ← Prev
    </button>
    <button onClick={() => setCurrentIndex(prev => (prev + 1) % totalImages)}>
      Next →
    </button>
  </div>

  <div className="autoplay-controls">
    <button onClick={() => setIsAutoplay(!isAutoplay)}>
      {isAutoplay ? 'Pause' : 'Play'}
    </button>
  </div>
</div>

Explanation:

  • Prev button: If index is 0, jump to last image, otherwise subtract 1
  • Next button: Use modulo operator to wrap around
  • Autoplay button: Toggle the current autoplay state

Progress Indicator:

<div className="progress-section">
  <div className="progress-info">
    <span>Frame {currentIndex + 1} of {totalImages}</span>
    <span>{Math.round((currentIndex / (totalImages - 1)) * 100)}%</span>
  </div>
  <div className="progress-bar">
    <div 
      className="progress-fill"
      style={{ width: `${(currentIndex / (totalImages - 1)) * 100}%` }}
    />
  </div>
</div>

Explanation:

  • Calculate progress percentage based on current index
  • Progress bar width changes dynamically using inline styles
  • Shows both frame number and percentage

Step 10: How to Build This Yourself

1. Start with Basic Structure:

const Viewer360 = () => {
  const [currentIndex, setCurrentIndex] = useState(0);
  const totalImages = 36;
  
  return (
    <div>
      <img src={`/images/img_${currentIndex + 1}.jpg`} />
    </div>
  );
};

2. Add Navigation:

<button onClick={() => setCurrentIndex(prev => (prev + 1) % totalImages)}>
  Next
</button>

3. Add Dragging:

const [isDragging, setIsDragging] = useState(false);
const [lastMouseX, setLastMouseX] = useState(0);

const handleMouseDown = (e) => {
  setIsDragging(true);
  setLastMouseX(e.clientX);
};

4. Add Autoplay:

useEffect(() => {
  const interval = setInterval(() => {
    setCurrentIndex(prev => (prev + 1) % totalImages);
  }, 400);
  
  return () => clearInterval(interval);
}, []);

5. Add Styling with CSS

Key Concepts to Understand:

  1. State Management: How React tracks changing data
  2. Event Handling: Responding to user interactions
  3. useEffect: Managing side effects and cleanup
  4. Modulo Operator (%): For wrapping around arrays
  5. Event Listeners: Document-level event handling
  6. Conditional Rendering: Showing/hiding elements based on state

This structure allows you to create any 360° viewer by just changing the image paths and total count!

Releases

No releases published

Packages

No packages published