Skip to main content

PhotoCollage Component

An interactive, animated photo collage component that displays vintage-styled postcards in a dynamic layout. Users can navigate through photos using left/right arrow buttons or touch swipes with smooth, physics-based animations.

http://localhost:3000

Photo #1, Norman, Okla.

1

DEMO-001

Photo #2, Norman, Okla.

2

DEMO-002

Photo #3, Norman, Okla.

3

DEMO-003

Photo #4, Norman, Okla.

4

DEMO-004

Photo #5, Norman, Okla.

5

DEMO-005

Photo #6, Norman, Okla.

6

DEMO-006

What is PhotoCollage?

PhotoCollage is a React component that creates an engaging visual experience by displaying multiple photo cards in a layered, 3D-style arrangement. It's perfect for showcasing photo galleries, portfolios, or event highlights with a vintage postcard aesthetic.

Key Features

  • 6-Card Collage Layout - Displays multiple cards in an aesthetically pleasing arrangement
  • Smooth Animations - Physics-based spring animations powered by Framer Motion
  • Interactive Navigation - Left/right arrows to shuffle through photos
  • Touch Support - Swipe gestures for mobile devices
  • Responsive Design - Adapts to different screen sizes
  • Vintage Aesthetic - Postcard styling with custom fonts and colors
  • Infinite Scrolling - Photos cycle continuously in both directions
  • Performance Optimized - GPU-accelerated animations with proper throttling

Quick Start

Basic Usage

The simplest way to use PhotoCollage:

import PhotoCollage from './components/photoCollage/photoCollage';

function MyPage() {
return <PhotoCollage />;
}

That's it! The component handles all state management and animations internally. No configuration needed to get started.

In a Page Layout

You can easily integrate PhotoCollage into any page:

import PhotoCollage from './components/photoCollage/photoCollage';

function GalleryPage() {
return (
<div>
<h1>My Photo Gallery</h1>
<p>Check out our latest photos:</p>
<PhotoCollage />
</div>
);
}

Component API

PhotoCollage Component

The main PhotoCollage component accepts no props. It's a self-contained component that manages all its own state.

<PhotoCollage />

Why no props? The component is designed to be plug-and-play. All configuration happens through the internal files (see Customization section).

Internal Components

While you typically won't use these directly, understanding the internal structure helps with customization:

Postcard Component

The individual card that displays each photo.

Props:

PropTypeRequiredDescription
imageUrlstringNoURL/path to the image file
titlestringYesTitle text displayed above the image
footerstringYesFooter text displayed below the image (bottom right)
demoNumbernumberNoDemo mode: displays a number instead of an image

Example:

import { Postcard } from './photoCollageComponents/postcard';

<Postcard
imageUrl="/path/to/image.jpg"
title="My Photo Title"
footer="PHOTO-001"
/>

ShuffleButton Component

The navigation arrow buttons (left/right). This component is used internally by PhotoCollage and typically doesn't need to be used directly.

Note: You typically won't use this component directly - it's used internally by PhotoCollage.

File Structure

Understanding the component structure helps with customization:

src/components/photoCollage/
├── photoCollage.tsx # Main component (use this!)
├── photoCollageComponents/
│ ├── postcard.tsx # Individual card component
│ ├── shuffleButton.tsx # Navigation button component
│ ├── card.ts # TypeScript types and interfaces
│ ├── cardFramerVariants.ts # Animation definitions
│ ├── cardShuffleLogic.ts # Shuffle algorithm logic
│ ├── photoGallery.ts # Photo data management
│ └── config.ts # Configuration constants

Customization

Adding Your Own Photos

To add your own photos, edit src/components/photoCollage/photoCollageComponents/photoGallery.ts:

// 1. Import your images
import MyPhoto1 from '../assets/my-photo-1.jpg';
import MyPhoto2 from '../assets/my-photo-2.jpg';

// 2. Add them to the photoImages array
export const photoImages: PhotoData[] = [
{
path: MyPhoto1,
title: 'My First Photo',
footer: 'PHOTO-001',
},
{
path: MyPhoto2,
title: 'My Second Photo',
footer: 'PHOTO-002',
},
// ... add more photos
];

Important Notes:

  • Place images in your project's assets directory
  • Use relative imports appropriate for your project structure
  • Each photo needs path, title, and footer properties
  • The path should be the imported image, not a string URL

Customizing Card Styling

Edit src/components/photoCollage/photoCollageComponents/Postcard.tsx to change:

  • Background color: Modify configSettings.FRAMED_CARD_BG in Config.ts
  • Fonts: Change the fontFamily in the style objects (currently 'American Typewriter' for title, 'Courier New' for footer)
  • Text colors: Update the text-gray-700 and text-gray-600 Tailwind classes
  • Border: Modify the shadow-2xl class for different shadow effects

Customizing Animation Speed

Edit src/components/photoCollage/photoCollageComponents/Config.ts:

export default {
SHUFFLE_DELAY: 300, // Milliseconds - delay between shuffles
FRAMED_CARD_BG: '#f4ece1', // Background color
};

Increase SHUFFLE_DELAY for slower animations, decrease for faster ones.

Customizing Button Appearance

Edit src/components/photoCollage/photoCollageComponents/ShuffleButton.tsx:

  • Opacity: Change opacity-20 class to make buttons more or less visible
  • Hover opacity: Modify opacity: 0.8 in whileHover to change hover effect
  • Size: Adjust w-16 h-16 md:w-20 md:h-20 classes to change button size
  • Position: Modify mr-16 and ml-16 in DIRECTION_CONFIG to change spacing from cards

Customizing Card Positions

The 6 card positions are defined in Card.ts. Each position has:

  • top and left coordinates (as CSS percentages)
  • rotate angle (in degrees)
  • zIndex stacking order (0-5, higher is in front)

To modify positions, edit the POSITION_CONFIGS object in Card.ts. Be careful when changing positions as it affects the visual balance of the collage.

How It Works

Architecture Overview

PhotoCollage (Main Component)
├── State Management (React hooks)
│ ├── cards: Card[] - Tracks 6 cards and their positions
│ ├── animationStates: CardAnimationMap - Controls animations
│ ├── isInView: boolean - Triggers entrance animations
│ └── hasCompletedEntrance: boolean - Tracks animation completion

├── ShuffleButton (Left) - Backward navigation
├── motion.div (Container) - Touch event handlers
│ └── [6x] motion.div (Card)
│ └── Postcard - Individual photo display
└── ShuffleButton (Right) - Forward navigation

Card Positions

The component uses 6 fixed positions for cards:

PositionZ-IndexDescription
center5Front-most card (always visible)
topLeft4Behind center, left side
topRight3Behind center, right side
bottomLeft2Back layer, left side
bottomRight1Back-most card, right side
centerBack0Hidden behind center (buffer card)

The centerBack position is used as a buffer - it holds the next photo that will appear when you navigate forward.

Animation States

Each card can be in one of these animation states:

  • OFFSCREEN - Initial state before page load
  • ONSCREEN - Animating onto screen during page load
  • MOVE_TO_POSITION - Smoothly transitioning to a new position
  • FLY_LEFT - Flying off screen to the left
  • FLY_RIGHT - Flying off screen to the right
  • IDLE - At rest in assigned position

Shuffle Logic

When you click a button or swipe:

Forward Shuffle (Right Button/Swipe Left):

  1. Center card flies right off screen
  2. All other cards move forward one position
  3. A new card appears from centerBack
  4. Photo updates when card reaches center

Backward Shuffle (Left Button/Swipe Right):

  1. Center card flies left off screen
  2. All other cards move backward one position
  3. CenterBack card moves to center
  4. Photo updates before shuffle

Touch/Swipe Support

The component detects touch gestures on mobile devices:

  • Swipe Left → Forward shuffle (same as right button)
  • Swipe Right → Backward shuffle (same as left button)
  • Minimum swipe distance: 50 pixels
  • Horizontal priority: Only triggers if horizontal movement is greater than vertical movement

This prevents accidental shuffles when scrolling vertically.

Examples

Basic Usage

import PhotoCollage from './components/photoCollage/photoCollage';

function Gallery() {
return <PhotoCollage />;
}

In a Page Layout

import PhotoCollage from './components/photoCollage/photoCollage';

function EventPage() {
return (
<div>
<h1>Event Photos</h1>
<p>Check out photos from our latest event:</p>
<PhotoCollage />
</div>
);
}

With Custom Wrapper

import PhotoCollage from './components/photoCollage/photoCollage';

function StyledGallery() {
return (
<div className="my-custom-wrapper">
<PhotoCollage />
</div>
);
}

Common Use Cases

Perfect for showcasing event photos, team photos, or product images in an engaging way.

Portfolio Showcase

Display your work in an interactive format that stands out from traditional galleries.

Event Highlights

Showcase photos from conferences, hackathons, or meetups with smooth navigation.

Product Showcase

Display product images in a unique, memorable way that encourages exploration.

Troubleshooting

Images Not Showing

Problem: Cards show numbers instead of images.

Solution:

  1. Check that images are imported correctly in PhotoGallery.ts
  2. Verify image paths are correct and match your project structure
  3. Ensure images are in your project's assets directory
  4. Check browser console for import errors
  5. Make sure you're using imported images, not string paths

Buttons Not Visible

Problem: Navigation arrows don't appear.

Solution:

  1. Check that hasCompletedEntrance state is true (buttons appear after entrance animation)
  2. Verify button opacity isn't set to 0
  3. Check z-index isn't hiding buttons behind cards
  4. Ensure viewport is large enough (buttons may be hidden on very small screens)
  5. Buttons start at 20% opacity - hover to see them more clearly

Animations Not Working

Problem: Cards don't animate when clicking buttons.

Solution:

  1. Check browser console for errors
  2. Verify Framer Motion is installed: npm install motion
  3. Check that buttonsDisabled isn't stuck at true
  4. Verify isAnimatingRef.current isn't blocking animations
  5. Wait for the entrance animation to complete before clicking buttons

Touch Swipes Not Working

Problem: Swipe gestures don't trigger shuffles.

Solution:

  1. Ensure you're on a touch device or using browser dev tools touch simulation
  2. Check that hasCompletedEntrance is true
  3. Verify swipe distance is at least 50 pixels
  4. Ensure swipe is primarily horizontal (not vertical)
  5. Try swiping more slowly - very fast swipes might not register

Performance Issues

Problem: Animations are laggy or choppy.

Solution:

  1. Reduce number of photos in gallery (recommended: 12-24 photos)
  2. Optimize image sizes (use WebP format, compress images)
  3. Check GPU acceleration is enabled in browser
  4. Reduce animation complexity in CardFramerVariants.ts if needed
  5. Ensure images aren't too large (avoid 4K images)

Best Practices

Image Optimization

  • Use WebP format for better compression and faster loading
  • Resize images to appropriate dimensions (don't use 4K images for web)
  • Compress images before adding to your assets directory
  • Aim for images under 500KB each for best performance

Photo Count

  • Recommended: 12-24 photos for best performance
  • Minimum: 6 photos (one per card)
  • Maximum: No hard limit, but more photos means slower initial load

Accessibility

  • Buttons have proper aria-label attributes for screen readers
  • Images should have descriptive alt text (set in title prop)
  • Consider adding keyboard navigation for better accessibility
  • Ensure sufficient color contrast for text

Mobile Considerations

  • Touch swipes work on mobile devices
  • Buttons are visible but subtle (20% opacity) to not distract from content
  • Responsive sizing adapts to screen size automatically
  • Test on actual mobile devices for best experience

Advanced Topics

Custom Animation Variants

To customize animations, edit CardFramerVariants.ts. Each animation state has its own variant definition using Framer Motion's variant system. You can modify spring physics, durations, and easing functions.

Custom Shuffle Logic

The shuffle algorithm is in CardShuffleLogic.ts. You can modify:

  • How cards move between positions
  • Z-index rotation logic
  • Animation state transitions

Be careful when modifying shuffle logic as it affects the core functionality.

State Management

The component uses React hooks for state:

  • useState for reactive state (cards, animations, etc.)
  • useRef for synchronous checks (prevents race conditions)
  • useEffect for side effects (entrance animations)

TypeScript Types

All types are defined in Card.ts. Key types:

  • Card - Individual card object with id, position, zIndex, and photo index
  • CardPosition - Enum of 6 positions (center, topLeft, etc.)
  • CardId - Enum of 6 card IDs (CARD_A through CARD_F)
  • AnimationState - Enum of animation states
  • PhotoData - Photo information structure (path, title, footer)

API Reference Summary

PhotoCollage

<PhotoCollage />

Props: None

Returns: JSX.Element

Postcard

<Postcard
imageUrl?: string
title: string
footer: string
demoNumber?: number
/>

ShuffleButton

<ShuffleButton
direction: 'left' | 'right'
hasCompletedEntrance: boolean
animationComplete: boolean
onAnimationComplete: () => void
cards: Card[]
buttonsDisabled: boolean
setCards: (cards: Card[]) => void
setAnimationStates: (states: CardAnimationMap) => void
setButtonsDisabled: (disabled: boolean) => void
isAnimatingRef: MutableRefObject<boolean>
swapPhotoBackShuffle?: (cards: Card[]) => Card[]
swapPhotoOnCardID?: (cardId: CardId, cards: Card[]) => Card[]
/>

Next Steps

  • Add your own photos to the gallery by editing PhotoGallery.ts
  • Customize the styling to match your brand
  • Experiment with animation speeds in Config.ts
  • Explore the shuffle logic to understand how it works
  • Check the troubleshooting section if you encounter issues

Need help? Check the troubleshooting section or open an issue on GitHub.