IT/nextjs 기초 내 공부

쉬운 NextJS 08: 네이티브 앱과 같은 페이지 전환 애니메이션 구현하기

higold 2024. 11. 12. 17:05

Next.js에서 네이티브 앱과 같은 페이지 전환 애니메이션 구현하기

목차

  1. 소개
  2. 페이지 이동 방향 관리
  3. Router Wrapper 구현
  4. 애니메이션 구현
  5. 최적화 고려사항

소개

네이티브 앱과 같은 자연스러운 페이지 전환 애니메이션을 웹에서 구현하는 방법을 알아보겠습니다. 새로운 페이지로 이동할 때는 오른쪽에서 왼쪽으로, 이전 페이지로 돌아갈 때는 왼쪽에서 오른쪽으로 슬라이딩되는 애니메이션을 구현해보겠습니다.

페이지 이동 방향 관리

Router Wrapper Context

// contexts/RouterWrapperContext.ts
import { createContext } from 'react';

type Direction = 'forward' | 'backward';

interface RouterWrapperContextType {
  direction: Direction;
  setDirection: (direction: Direction) => void;
}

export const RouterWrapperContext = createContext<RouterWrapperContextType>({
  direction: 'forward',
  setDirection: () => {}
});

Router Wrapper Provider

// components/RouterWrapper.tsx
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { RouterWrapperContext } from '../contexts/RouterWrapperContext';

export function RouterWrapper({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  const [direction, setDirection] = useState<'forward' | 'backward'>('forward');

  // 라우터 메소드 래핑
  const wrappedPush = (url: string) => {
    setDirection('forward');
    router.push(url);
  };

  const wrappedBack = () => {
    setDirection('backward');
    router.back();
  };

  return (
    <RouterWrapperContext.Provider value={{ direction, setDirection }}>
      {children}
    </RouterWrapperContext.Provider>
  );
}

Extended Link Component

// components/ExtendedLink.tsx
import { useContext } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { RouterWrapperContext } from '../contexts/RouterWrapperContext';

export function ExtendedLink({ href, children, ...props }: { href: string; children: React.ReactNode }) {
  const { setDirection } = useContext(RouterWrapperContext);
  const router = useRouter();

  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault();
    setDirection('forward');
    router.push(href);
  };

  return (
    <Link href={href} onClick={handleClick} {...props}>
      {children}
    </Link>
  );
}

애니메이션 구현

Template Component

// components/PageTransitionTemplate.tsx
'use client';

import { motion } from 'framer-motion';
import { useContext } from 'react';
import { RouterWrapperContext } from '../contexts/RouterWrapperContext';

export default function PageTransitionTemplate({ children }: { children: React.ReactNode }) {
  const { direction } = useContext(RouterWrapperContext);

  const variants = {
    initial: {
      x: direction === 'forward' ? '100vw' : '-100vw',
    },
    animate: {
      x: 0
    },
    exit: {
      x: direction === 'forward' ? '-100vw' : '100vw',
    }
  };

  return (
    <>
      <motion.div
        key="main-content"
        variants={variants}
        initial="initial"
        animate="animate"
        exit="exit"
        transition={{ type: 'spring', stiffness: 300, damping: 30 }}
      >
        {children}
      </motion.div>

      <motion.div
        key="cached-page"
        className="cached-page"
        initial={{ x: 0 }}
        animate={{
          x: direction === 'forward' ? '-100vw' : '100vw'
        }}
        transition={{ type: 'spring', stiffness: 300, damping: 30 }}
      >
        Cached Page Content
      </motion.div>
    </>
  );
}

스타일링

/* styles/transitions.css */
.cached-page {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
}

사용 방법

// app/layout.tsx
import { RouterWrapper } from '../components/RouterWrapper';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <RouterWrapper>
      <html>
        <body>{children}</body>
      </html>
    </RouterWrapper>
  );
}

// app/example/page.tsx
import PageTransitionTemplate from '../../components/PageTransitionTemplate';

export default function ExamplePage() {
  return (
    <PageTransitionTemplate>
      <h1>Example Page</h1>
      <ExtendedLink href="/">Go to Home</ExtendedLink>
    </PageTransitionTemplate>
  );
}

최적화 고려사항

  1. 캐시된 페이지 관리

    • 애니메이션 완료 후 캐시된 페이지 컴포넌트 언마운트
    • 메모리 사용량 최적화
  2. 성능 최적화

    • will-change 속성 활용
    • transform 애니메이션 사용으로 리페인트 최소화
  3. 접근성

    • prefers-reduced-motion 미디어 쿼리 지원
    • 적절한 ARIA 속성 추가

결론

이 구현 방식의 장점:

  • 네이티브 앱과 유사한 사용자 경험
  • 방향에 따른 자연스러운 전환 애니메이션
  • 확장 가능한 구조

주의사항:

  • 브라우저 호환성 고려
  • 성능 모니터링 필요
  • 접근성 준수

참고 문헌

  1. Next.js 공식 문서 - Page Transitions
  2. Framer Motion 공식 문서
  3. React Router 공식 문서
  4. MDN Web Docs - Web Animations
  5. Web.dev - Animation Performance

태그

#NextJS #React #Animation #PageTransitions #FramerMotion #WebDevelopment #Frontend #JavaScript #TypeScript #UX #Performance #WebAnimation #RouterTransitions #MobileFirst #WebDevelopment #ReactHooks #ContextAPI

반응형