Skip to content

feat: The Awesome Gallery #127

@Aryan-Techie

Description

@Aryan-Techie

Building a Beautiful Gallery for Magic Portfolio

So I wanted to add a photo gallery to my portfolio website, and let me tell you - it turned out way better than I expected!
Here's the story of how I built it, step by step.

What I Wanted to Achieve

I had a bunch of photos from my journey - campus life at IIT Patna, coding sessions, travel moments, creative projects. I wanted a clean, modern gallery that would:

  • Show my photos in a beautiful masonry layout (like Pinterest)
  • Be fast and responsive on mobile
  • Have a cool modal popup for viewing full images
  • Work smoothly with touch gestures
  • Load images lazily for better performance

The File Structure

Here's the structure:

src/
├── app/gallery/
│   └── page.tsx                    # Main gallery page
├── components/gallery/
│   ├── MasonryGrid.tsx            # The magic happens here
│   └── Gallery.module.scss        # All the styling
└── resources/
    └── content.js                 # Where I define my gallery data

Step 1: Setting Up the Gallery Data

First, I added my gallery configuration to content.js. This is where I keep all my site's content organized:

const gallery = {
  path: "/gallery",
  label: "Gallery", 
  title: "Visual Stories",
  subtitle: "Capturing moments and memories...",
  description: "Photo gallery showcasing my journey...",
  images: [
    {
      src: "/images/gallery/pastry-cafe-iit.jpg",
      alt: "Pastry and coffee at IIT Patna cafe",
      title: 'Pastry',
      year: '2025'
    },
    // ... more images
  ]
}

Each image has:

  • src: Path to the image file
  • alt: Description for accessibility
  • title: A short title that shows in the modal
  • year: When I took the photo

Step 2: Creating the Gallery Page

The gallery page (src/app/gallery/page.tsx) is pretty straightforward.

  1. Sets up the metadata for SEO
  2. Renders a heading with title and subtitle
  3. Displays the MasonryGrid component
export default function Gallery() {
  return (
    <Column maxWidth="l">
      <Heading marginBottom="xs" variant="display-strong-s">
        {gallery.title}
      </Heading>
      <Heading marginBottom="l" variant="heading-default-xl" as="h2">
        {gallery.subtitle}
      </Heading>
      <MasonryGrid />
    </Column>
  );
}

Simple, clean, does the job.

Step 3: The Heart - MasonryGrid Component

This is where the real magic happens. The MasonryGrid.tsx component does a LOT:

Key Features I Built:

1. Masonry Layout

  • Uses react-masonry-css for the Pinterest-style grid
  • Responsive columns: 3 on desktop, 2 on mobile
  • Images arrange themselves naturally based on their aspect ratios

2. Smart Image Loading

  • Lazy loading: Images only load when you scroll to them
  • Automatically detects image dimensions and aspect ratios
  • Handles loading states and errors gracefully
  • Retry mechanism if an image fails to load

3. Image Classification
The component automatically figures out if each image is:

  • Portrait (tall)
  • Landscape (wide)
  • Square

Then it sets the right aspect ratio for smooth loading btw I still feel that there is a room for lot of improvemnts and basically as of now it just works.

4. Shuffled Display
Every time you visit, the images are in a different order using the Fisher-Yates shuffle algorithm. Keeps things fresh! (just my thing ;) )

5. Modal Popup
Click any image and get a beautiful full-screen modal with:

  • High-quality image display
  • Navigation arrows (or really good swipe on mobile)
  • Keyboard support (arrow keys, escape)
  • Image title and year overlay below the image

6. Mobile-First Design

  • Touch gestures: swipe left/right to navigate
  • Pull down to close the image popup modal
  • Optimized touch targets
  • Smooth animations and feedback (I think so, can be improved)

The Code Structure

// Main component with state management
export default function MasonryGrid({ images, columns, loadingText }) {
  // State for modal, loading, errors, etc.
  const [selectedImage, setSelectedImage] = useState(null);
  const [imageData, setImageData] = useState(new Map());
  
  // Shuffle images on load
  const shuffledImages = useMemo(() => shuffleArray(images), [images]);
  
  // Individual image loading logic
  const loadImageDimensions = useCallback(async (image) => {
    // Load image, get dimensions, calculate aspect ratio
  });
  
  // Render the masonry grid
  return (
    <Masonry breakpointCols={breakpointColumnsObj}>
      {shuffledImages.map((image, index) => (
        <LazyImage 
          key={image.src}
          image={image}
          index={index}
          onOpenModal={openModal}
          // ... other props
        />
      ))}
    </Masonry>
  );
}

The Lazy Loading Component

function LazyImage({ image, index, onOpenModal }) {
  const [isVisible, setIsVisible] = useState(false);
  
  // Intersection Observer for lazy loading
  useEffect(() => {
    const observer = new IntersectionObserver(/* ... */);
    // Only load when image enters viewport
  }, []);
  
  return (
    <div onClick={() => onOpenModal(image, index)}>
      {isVisible ? (
        <Media src={image.src} alt={image.alt} />
      ) : (
        <div>Loading placeholder</div>
      )}
    </div>
  );
}

Step 4: Styling with SCSS

The Gallery.module.scss file handles all the visual polish:

Grid Layout

.masonryGrid {
    display: flex;
    margin-left: calc(-1 * var(--static-space-16));
    width: 100%;
}

.masonryGridColumn {
    padding-left: var(--static-space-16);
    background-clip: padding-box;
}

Modal Styling

.modal {
    background: rgba(0, 0, 0, 0.95);
    border: 1px solid var(--neutral-alpha-weak);
    border-radius: var(--radius-m-static, 8px);
    backdrop-filter: blur(20px);
}

Mobile Optimizations

.imageWrapper {
    @media (max-width: 768px) {
        min-height: 44px;  // Touch target size
        
        &:active {
            opacity: 0.8;
            transform: scale(0.98);
            transition: all 0.1s ease;
        }
    }
}

Step 5: Touch Interactions

One of the coolest parts was adding mobile touch gestures:

Swipe Navigation

  • Swipe left/right to go between images
  • Smooth animations with proper thresholds
  • Prevents accidental navigation on vertical scrolls

Pull-to-Close

  • Drag down on the modal image to close it
  • Visual feedback with scaling and opacity
  • Snaps back if you don't drag far enough

Implementation

const handleTouchMove = (e) => {
  const deltaX = touch.clientX - touchStart.x;
  const deltaY = touch.clientY - touchStart.y;
  
  // Pull-to-close: drag down to close
  if (deltaY < -100 && Math.abs(deltaY) > Math.abs(deltaX)) {
    closeModal();
  }
  
  // Horizontal swipe for navigation
  if (Math.abs(deltaX) > 50) {
    deltaX > 0 ? prevImage() : nextImage();
  }
};

The Result

What I ended up with is a gallery that:

✅ Loads fast with lazy loading
✅ Looks great on all devices
✅ Has smooth animations and interactions
✅ Shows my photos in their best light
✅ Provides excellent user experience
✅ Is accessible with keyboard navigation

Performance Optimizations

Image Loading Strategy

  • Only load images when they're about to be visible
  • Use intersection observer with 100px margin
  • Retry failed loads automatically
  • Show loading states and error handling

Memory Management

  • Clean up event listeners properly
  • Use useCallback and useMemo to prevent unnecessary re-renders
  • Efficient state updates with Maps and Sets

Mobile Performance

  • Touch-action optimizations
  • Prevent scroll bounce on iOS
  • Optimized transform animations
  • Proper viewport handling

Future Improvements

here are some ideas for making it even better:

  • Add image categories/filtering
  • Implement infinite scroll for large galleries
  • Add sharing functionality
  • Include EXIF data display
  • Add zoom functionality in modal
  • Implement favorites/likes system

I might work on these in near future. (hope so)

and yes all the files are availabe in attached -> awesome-gallery.zip


The whole thing took me about a week to build and polish, but I'm really happy with how it turned out. It's one of those features that makes the whole site feel more personal and engaging.

If you're building something similar, my advice is: start simple, then add the fancy stuff. The core functionality (display images in a grid) is straightforward. The magic is in the details - smooth animations, proper loading states, and thoughtful mobile interactions. (I love to touch up things my way. Hope you like it and might find it helpful.)

That's the story of my gallery! From a folder full of photos to a polished, interactive experience that showcases my journey as a developer and creator.

Peace.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions