SSGOI

スライドアニメーション

要素を特定の方向にスライドさせて動的な移動効果を作成します

スライドアニメーション

スライド(Slide)アニメーションは、要素を特定の方向から滑らかに表示または非表示にします。方向性のある遷移が必要な場合に効果的です。

基本的な使い方

import { transition } from '@ssgoi/react';
import { slide } from '@ssgoi/react/transitions';

function Component() {
  const [isVisible, setIsVisible] = useState(true);
  
  return (
    <div>
      {isVisible && (
        <div ref={transition({ key: 'slide-element', ...slide() })}>
          スライドアニメーションが適用された要素
        </div>
      )}
    </div>
  );
}

オプション

interface SlideOptions {
  direction?: 'left' | 'right' | 'up' | 'down';  // スライド方向(デフォルト: 'left')
  distance?: number | string;   // 移動距離(デフォルト: 100)
  opacity?: number;            // 開始時の不透明度(デフォルト: 0)
  fade?: boolean;              // フェード効果の追加(デフォルト: true)
  axis?: 'x' | 'y';          // 移動軸(directionの代替)
  spring?: {
    stiffness?: number;        // スプリングの硬さ(デフォルト: 400)
    damping?: number;          // 減衰係数(デフォルト: 35)
  };
}

オプション説明

  • direction: スライド方向
    • 'left': 左から登場
    • 'right': 右から登場
    • 'up': 上から登場
    • 'down': 下から登場
  • distance: 移動距離(ピクセルまたはCSS単位)
  • opacity: 開始時の不透明度(0-1)
  • fade: スライドと同時にフェード効果を使用
  • axis: シンプルな軸指定('x' または 'y')
  • spring: スプリング物理設定

使用例

方向別スライド

// 左からスライド
const slideLeft = slide({ 
  direction: 'left' 
});

// 右からスライド
const slideRight = slide({ 
  direction: 'right' 
});

// 上からスライド
const slideUp = slide({ 
  direction: 'up' 
});

// 下からスライド
const slideDown = slide({ 
  direction: 'down' 
});

距離の調整

// 短い距離
const shortSlide = slide({
  direction: 'left',
  distance: 50  // 50pxのみ移動
});

// 長い距離
const longSlide = slide({
  direction: 'right',
  distance: '100vw'  // 画面幅分移動
});

// rem単位の使用
const remSlide = slide({
  direction: 'up',
  distance: '5rem'
});

フェードなしのスライド

const slideNoFade = slide({
  direction: 'left',
  fade: false,      // フェード効果を削除
  opacity: 1        // 完全不透明な状態を維持
});

実用的な活用例

サイドバーメニュー

function Sidebar({ isOpen, onClose }) {
  return (
    <>
      {isOpen && (
        <>
          {/* 背景オーバーレイ */}
          <div 
            className="fixed inset-0 bg-black/50 z-40"
            onClick={onClose}
          />
          
          {/* サイドバー */}
          <div 
            ref={transition({ 
              key: 'sidebar', 
              ...slide({ 
                direction: 'left', 
                distance: 300,
                fade: false 
              }) 
            })}
            className="fixed left-0 top-0 h-full w-72 bg-white shadow-lg z-50"
          >
            <nav className="p-4">
              <h2>メニュー</h2>
              {/* メニューアイテム */}
            </nav>
          </div>
        </>
      )}
    </>
  );
}

通知トースト

function Toast({ message, isVisible }) {
  return (
    <>
      {isVisible && (
        <div 
          ref={transition({ 
            key: 'toast', 
            ...slide({ 
              direction: 'up', 
              distance: 100 
            }) 
          })}
          className="fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg"
        >
          {message}
        </div>
      )}
    </>
  );
}

タブコンテンツの切り替え

function TabContent({ activeTab }) {
  const getSlideDirection = (tab) => {
    const tabs = ['tab1', 'tab2', 'tab3'];
    const currentIndex = tabs.indexOf(activeTab);
    const targetIndex = tabs.indexOf(tab);
    return currentIndex < targetIndex ? 'left' : 'right';
  };
  
  return (
    <div className="relative overflow-hidden h-64">
      {activeTab === 'tab1' && (
        <div 
          ref={transition({ 
            key: 'tab1', 
            ...slide({ direction: getSlideDirection('tab1') }) 
          })}
          className="absolute inset-0 p-4"
        >
          タブ1の内容
        </div>
      )}
      
      {activeTab === 'tab2' && (
        <div 
          ref={transition({ 
            key: 'tab2', 
            ...slide({ direction: getSlideDirection('tab2') }) 
          })}
          className="absolute inset-0 p-4"
        >
          タブ2の内容
        </div>
      )}
    </div>
  );
}

カルーセルスライド

function Carousel({ images, currentIndex }) {
  return (
    <div className="relative w-full h-96 overflow-hidden">
      {images.map((image, index) => (
        index === currentIndex && (
          <img
            key={index}
            ref={transition({ 
              key: `slide-${index}`, 
              ...slide({ 
                direction: 'left',
                distance: '100%',
                spring: { stiffness: 300, damping: 30 }
              }) 
            })}
            src={image}
            alt={`スライド ${index + 1}`}
            className="absolute inset-0 w-full h-full object-cover"
          />
        )
      ))}
    </div>
  );
}

高度な活用

軸ベースのスライド

// X軸スライド(左右)
const slideX = slide({
  axis: 'x',  // directionの代わりにaxisを使用
  distance: 200
});

// Y軸スライド(上下)
const slideY = slide({
  axis: 'y',
  distance: 150
});

連続アニメーション

function SequentialSlides() {
  const [step, setStep] = useState(0);
  
  return (
    <div>
      {step >= 0 && (
        <div 
          ref={transition({ 
            key: 'step-1', 
            ...slide({ direction: 'right', distance: 50 }) 
          })}
        >
          ステップ1
        </div>
      )}
      
      {step >= 1 && (
        <div 
          ref={transition({ 
            key: 'step-2', 
            ...slide({ direction: 'right', distance: 50 }) 
          })}
        >
          ステップ2
        </div>
      )}
      
      <button onClick={() => setStep(step + 1)}>次へ</button>
    </div>
  );
}

レスポンシブスライド

function ResponsiveSlide() {
  const isMobile = window.innerWidth < 768;
  
  const responsiveSlide = slide({
    direction: isMobile ? 'up' : 'left',
    distance: isMobile ? 50 : 100,
    spring: { 
      stiffness: isMobile ? 500 : 400,
      damping: 35 
    }
  });
  
  return (
    <div ref={transition({ key: 'responsive', ...responsiveSlide })}>
      レスポンシブスライド
    </div>
  );
}

パフォーマンス最適化

  • transform: translate()はGPUアクセラレーションを使用するため、パフォーマンスが優れています
  • レイアウトを変更しないため、リフローが発生しません
  • 多数の要素に同時に適用してもスムーズなアニメーションを維持

パフォーマンスのヒント

// 良い例: transformを使用
const goodSlide = slide(); // transform: translateX/Yを使用

// 避けるべき例: positionを直接変更
const badSlide = {
  in: (element) => ({
    tick: (progress) => {
      // リフローが発生!
      element.style.left = `${(1 - progress) * -100}px`;
    }
  })
};

アクセシビリティの考慮事項

<div 
  ref={transition({ 
    key: 'accessible-slide', 
    ...slide() 
  })}
  role="region"
  aria-live="polite"
  aria-label="スライドコンテンツ"
>
  アクセシブルなスライドコンテンツ
</div>

推奨される使用例

  • ナビゲーションメニュー: サイドバー、ドロップダウン
  • 通知/トースト: 画面端からの表示
  • タブ/ステップ切り替え: 方向性のあるコンテンツ遷移
  • 画像ギャラリー: スライドショー、カルーセル
  • フォームステップ: 多段階フォームのステップ間遷移