Pinterestトランジション

カードが画面全体を満たしながら拡張する没入型トランジション

Pinterestトランジション

Pinterestトランジションは、ギャラリーの小さなアイテムが全画面に拡張する**拡張型トランジション(Expansion Transition)**です。ユーザーが選択したコンテンツが画面を満たしながら拡大され、強力な没入感と視覚的連続性を提供します。

デモ

Loading demo...

UXの原則

いつ使用するか?

Pinterestトランジションは視覚的コンテンツの拡大探索時に使用されます。

コンテンツの関係別適合性

콘텐츠 관계적합성설명
関連のないコンテンツ拡張する接続点がなく不適切
兄弟関係⚠️ギャラリー内のアイテム間移動時に検討可能
階層関係サムネイル→フルビューのような同じコンテンツの拡大ビューに最適

主な使用ケース

  • 画像ギャラリー: サムネイルから高解像度画像へ拡大
  • 製品カタログ: 製品グリッドから詳細情報へ拡張
  • メディアブラウジング: ビデオサムネイルからプレーヤーへ遷移
  • ポートフォリオ: 作品リストから全画面プレゼンテーションへ

なぜこのように動作するのか?

  1. 空間拡張メタファー: 小さな窓を通して大きな世界に入る感覚
  2. 自然なフォーカス移動: ユーザーの視線が自然に拡張する要素を追う
  3. コンテキスト保存: どこから来たかが明確で方向感覚を維持
  4. ドラマチックな効果: 拡張アニメーションがコンテンツの重要性を強調

モーションデザイン

拡張プロセス:
1. 選択されたカードの位置とサイズをキャプチャ
2. カードが画面中央へ移動しながら拡大
3. 同時に背景のフェードとスケール変化
4. 最終的に全画面を満たす

縮小プロセス:
1. 詳細画面が縮小しながら元の位置へ
2. 背景が再び現れてギャラリーが復元
3. 自然なスプリングアニメーションで定着

基本的な使い方

1. トランジション設定

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}>
      {/* アプリの内容 */}
    </Ssgoi>
  );
}

2. 要素のマーキング

ギャラリーと詳細ページにそれぞれ異なるdata属性を使用:

// ギャラリーページ
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>
  );
}

// 詳細ページ
function ItemDetail({ item }) {
  return (
    <div 
      data-pinterest-detail-key={item.id}
      className="detail-container"
    >
      <button onClick={() => navigate('/gallery')}>
        ✕ 閉じる
      </button>
      <img src={item.image} alt={item.title} />
      <article>
        <h1>{item.title}</h1>
        <p>{item.description}</p>
      </article>
    </div>
  );
}

実践的な活用例

1. メイソンリーギャラリー

Pinterestスタイルのメイソンリーレイアウト:

// メイソンリーグリッド
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>
  );
}

// 拡張ビュー
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. 製品カタログ

製品カードが詳細ページへ拡張:

// 製品グリッド
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>
  );
}

// 製品詳細
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">
            カートに追加
          </button>
        </div>
      </div>
    </div>
  );
}

カスタマイズ

スプリング設定

アニメーションの弾性と速度の調整:

pinterest({
  spring: {
    stiffness: 200,  // 低いほど滑らか
    damping: 25      // 高いほど速い定着
  }
})

注意事項

キー属性の区別

  • ギャラリー: data-pinterest-gallery-key
  • 詳細: data-pinterest-detail-key
  • 必ず異なる属性名を使用(ヒーローと差別化)

レイアウトの考慮事項

  • ギャラリーアイテムはpositionがstaticである必要があります
  • 詳細ページには十分な余白が必要
  • メイソンリーレイアウトと特に相性が良い

アクセシビリティ

  • ESCキーで詳細ページを閉じるサポート
  • フォーカストラップによるキーボードナビゲーションの改善
  • モーション削減設定時は即座に遷移

ベストプラクティス

✅ DO

  • 画像中心のコンテンツに使用
  • ギャラリーやカタログ形式に適用
  • 閉じるボタンを明確に提供
  • モバイルでも動作するようレスポンシブデザイン

❌ DON'T

  • テキスト中心のコンテンツには不適切
  • 多くの要素が同時に拡張されないように
  • 詳細ページのロードが遅い場合は使用を控える
  • 複雑なレイアウトでは予期しない動作の可能性