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.
Photo #1, Norman, Okla.
DEMO-001
Photo #2, Norman, Okla.
DEMO-002
Photo #3, Norman, Okla.
DEMO-003
Photo #4, Norman, Okla.
DEMO-004
Photo #5, Norman, Okla.
DEMO-005
Photo #6, Norman, Okla.
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:
| Prop | Type | Required | Description |
|---|---|---|---|
imageUrl | string | No | URL/path to the image file |
title | string | Yes | Title text displayed above the image |
footer | string | Yes | Footer text displayed below the image (bottom right) |
demoNumber | number | No | Demo 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, andfooterproperties - The
pathshould 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_BGinConfig.ts - Fonts: Change the
fontFamilyin the style objects (currently 'American Typewriter' for title, 'Courier New' for footer) - Text colors: Update the
text-gray-700andtext-gray-600Tailwind classes - Border: Modify the
shadow-2xlclass 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-20class to make buttons more or less visible - Hover opacity: Modify
opacity: 0.8inwhileHoverto change hover effect - Size: Adjust
w-16 h-16 md:w-20 md:h-20classes to change button size - Position: Modify
mr-16andml-16inDIRECTION_CONFIGto change spacing from cards
Customizing Card Positions
The 6 card positions are defined in Card.ts. Each position has:
topandleftcoordinates (as CSS percentages)rotateangle (in degrees)zIndexstacking 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:
| Position | Z-Index | Description |
|---|---|---|
center | 5 | Front-most card (always visible) |
topLeft | 4 | Behind center, left side |
topRight | 3 | Behind center, right side |
bottomLeft | 2 | Back layer, left side |
bottomRight | 1 | Back-most card, right side |
centerBack | 0 | Hidden 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 loadONSCREEN- Animating onto screen during page loadMOVE_TO_POSITION- Smoothly transitioning to a new positionFLY_LEFT- Flying off screen to the leftFLY_RIGHT- Flying off screen to the rightIDLE- At rest in assigned position
Shuffle Logic
When you click a button or swipe:
Forward Shuffle (Right Button/Swipe Left):
- Center card flies right off screen
- All other cards move forward one position
- A new card appears from centerBack
- Photo updates when card reaches center
Backward Shuffle (Left Button/Swipe Right):
- Center card flies left off screen
- All other cards move backward one position
- CenterBack card moves to center
- 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
Photo Gallery
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:
- Check that images are imported correctly in
PhotoGallery.ts - Verify image paths are correct and match your project structure
- Ensure images are in your project's assets directory
- Check browser console for import errors
- Make sure you're using imported images, not string paths
Buttons Not Visible
Problem: Navigation arrows don't appear.
Solution:
- Check that
hasCompletedEntrancestate istrue(buttons appear after entrance animation) - Verify button opacity isn't set to 0
- Check z-index isn't hiding buttons behind cards
- Ensure viewport is large enough (buttons may be hidden on very small screens)
- Buttons start at 20% opacity - hover to see them more clearly
Animations Not Working
Problem: Cards don't animate when clicking buttons.
Solution:
- Check browser console for errors
- Verify Framer Motion is installed:
npm install motion - Check that
buttonsDisabledisn't stuck attrue - Verify
isAnimatingRef.currentisn't blocking animations - Wait for the entrance animation to complete before clicking buttons
Touch Swipes Not Working
Problem: Swipe gestures don't trigger shuffles.
Solution:
- Ensure you're on a touch device or using browser dev tools touch simulation
- Check that
hasCompletedEntranceistrue - Verify swipe distance is at least 50 pixels
- Ensure swipe is primarily horizontal (not vertical)
- Try swiping more slowly - very fast swipes might not register
Performance Issues
Problem: Animations are laggy or choppy.
Solution:
- Reduce number of photos in gallery (recommended: 12-24 photos)
- Optimize image sizes (use WebP format, compress images)
- Check GPU acceleration is enabled in browser
- Reduce animation complexity in
CardFramerVariants.tsif needed - 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-labelattributes for screen readers - Images should have descriptive
alttext (set intitleprop) - 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:
useStatefor reactive state (cards, animations, etc.)useReffor synchronous checks (prevents race conditions)useEffectfor 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 indexCardPosition- Enum of 6 positions (center, topLeft, etc.)CardId- Enum of 6 card IDs (CARD_A through CARD_F)AnimationState- Enum of animation statesPhotoData- 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.