Pinterest Transition

Immersive transition with cards expanding to fill the screen

Pinterest Transition

The Pinterest transition is an expansion transition where small gallery items expand to full screen. The selected content enlarges to fill the entire screen, providing powerful immersion and visual continuity.

Demo

Loading demo...

UX Principles

When to Use?

Pinterest transitions are used for visual content exploration with expansion.

Content Relationship Suitability

콘텐츠 관계적합성설명
Unrelated ContentUnsuitable without expansion connection point
Sibling Relationship⚠️Considerable for navigation within gallery items
Hierarchical RelationshipOptimal for expanded views like thumbnail→fullview

Key Use Cases

  • Image Gallery: Expanding from thumbnails to high-resolution images
  • Product Catalog: Expanding from product grid to detail information
  • Media Browsing: Transitioning from video thumbnails to player
  • Portfolio: From work list to fullscreen presentation

Why Does It Work This Way?

  1. Spatial Expansion Metaphor: Feeling of entering a larger world through a small window
  2. Natural Focus Shift: User's gaze naturally follows the expanding element
  3. Context Preservation: Clear origin maintains spatial orientation
  4. Dramatic Effect: Expansion animation emphasizes content importance

Motion Design

Expansion Process:
1. Capture selected card position and size
2. Card moves to center while scaling up
3. Background fades and scales simultaneously
4. Finally fills entire screen

Contraction Process:
1. Detail screen shrinks back to original position
2. Background reappears restoring gallery
3. Settles with natural spring animation

Basic Usage

1. Transition Setup

import { Ssgoi } from '@ssgoi/react';
import { pinterest } from '@ssgoi/react/view-transitions';

const config = {
  transitions: [
    {
      from: '/gallery',
      to: '/gallery/*',
      transition: pinterest(),
      symmetric: true
    }
  ]
};

export default function App() {
  return (
    <Ssgoi config={config}>
      {/* App content */}
    </Ssgoi>
  );
}

2. Element Marking

Use different data attributes for gallery and detail pages:

// Gallery Page
function Gallery() {
  return (
    <div className="masonry-grid">
      {items.map(item => (
        <Link 
          key={item.id} 
          to={`/gallery/${item.id}`}
          data-pinterest-gallery-key={item.id}
          className="gallery-item"
        >
          <img src={item.image} alt={item.title} />
          <h3>{item.title}</h3>
        </Link>
      ))}
    </div>
  );
}

// Detail Page
function ItemDetail({ item }) {
  return (
    <div 
      data-pinterest-detail-key={item.id}
      className="detail-container"
    >
      <button onClick={() => navigate('/gallery')}>
        ✕ Close
      </button>
      <img src={item.image} alt={item.title} />
      <article>
        <h1>{item.title}</h1>
        <p>{item.description}</p>
      </article>
    </div>
  );
}

Practical Examples

Pinterest-style masonry layout:

// Masonry Grid
function MasonryGallery() {
  return (
    <div className="columns-2 md:columns-3 lg:columns-4 gap-4">
      {images.map((img, index) => (
        <div
          key={img.id}
          data-pinterest-gallery-key={img.id}
          onClick={() => navigate(`/image/${img.id}`)}
          className="break-inside-avoid mb-4 cursor-pointer 
                     hover:scale-105 transition-transform"
        >
          <img 
            src={img.url} 
            alt={img.title}
            className="w-full rounded-lg"
          />
          <div className="p-2">
            <p className="text-sm font-medium">{img.title}</p>
            <p className="text-xs text-gray-500">{img.author}</p>
          </div>
        </div>
      ))}
    </div>
  );
}

// Expanded View
function ImageView({ image }) {
  return (
    <div 
      data-pinterest-detail-key={image.id}
      className="fixed inset-0 bg-white z-50 overflow-auto"
    >
      <div className="max-w-4xl mx-auto p-4">
        <img 
          src={image.url} 
          alt={image.title}
          className="w-full rounded-lg shadow-xl"
        />
        <div className="mt-4">
          <h1 className="text-2xl font-bold">{image.title}</h1>
          <p className="text-gray-600 mt-2">{image.description}</p>
        </div>
      </div>
    </div>
  );
}

2. Product Catalog

Product card expanding to detail page:

// Product Grid
function ProductGrid() {
  return (
    <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
      {products.map(product => (
        <article
          key={product.id}
          data-pinterest-gallery-key={product.id}
          onClick={() => navigate(`/product/${product.id}`)}
          className="bg-white rounded-xl shadow-lg overflow-hidden
                     cursor-pointer hover:shadow-xl transition-shadow"
        >
          <img 
            src={product.image} 
            alt={product.name}
            className="w-full h-48 object-cover"
          />
          <div className="p-4">
            <h3 className="font-semibold">{product.name}</h3>
            <p className="text-lg font-bold mt-2">${product.price}</p>
          </div>
        </article>
      ))}
    </div>
  );
}

// Product Detail
function ProductDetail({ product }) {
  return (
    <div 
      data-pinterest-detail-key={product.id}
      className="min-h-screen bg-white"
    >
      <div className="grid md:grid-cols-2 gap-8 p-8">
        <img 
          src={product.image} 
          alt={product.name}
          className="w-full rounded-lg"
        />
        <div>
          <h1 className="text-3xl font-bold">{product.name}</h1>
          <p className="text-2xl font-bold mt-4">${product.price}</p>
          <p className="mt-4 text-gray-600">{product.description}</p>
          <button className="mt-6 bg-black text-white px-8 py-3 rounded-lg">
            Add to Cart
          </button>
        </div>
      </div>
    </div>
  );
}

Customization

Spring Settings

Adjust animation elasticity and speed:

pinterest({
  spring: {
    stiffness: 200,  // Lower for smoother motion
    damping: 25      // Higher for faster settling
  }
})

Important Notes

Key Attribute Distinction

  • Gallery: data-pinterest-gallery-key
  • Detail: data-pinterest-detail-key
  • Must use different attribute names (to differentiate from hero)

Layout Considerations

  • Gallery items should have static position
  • Detail pages need sufficient padding
  • Works especially well with masonry layouts

Accessibility

  • ESC key support to close detail page
  • Focus trap for improved keyboard navigation
  • Instant transition when motion is reduced

Best Practices

✅ DO

  • Use for image-centric content
  • Apply to gallery or catalog formats
  • Provide clear close button
  • Ensure responsive design for mobile

❌ DON'T

  • Unsuitable for text-heavy content
  • Avoid too many elements expanding simultaneously
  • Avoid use when detail page loading is slow
  • May cause unexpected behavior in complex layouts