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={`Slide ${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>

권장 사용 사례

  • 내비게이션 메뉴: 사이드바, 드롭다운
  • 알림/토스트: 화면 가장자리에서 등장
  • 탭/스텝 전환: 방향성 있는 콘텐츠 전환
  • 이미지 갤러리: 슬라이드쇼, 캐러셀
  • 폼 단계: 다단계 폼의 단계별 전환