[Next.js] 포트폴리오 웹 페이지 제작기 - 13. 로더 구현

olwooz·2023년 3월 23일
0
post-thumbnail

뭔가 포트폴리오 웹 페이지다 하면 또 로고가 빠질 수 없기에 처음 페이지에 접속할 때 로더에서 로고를 보여주고 싶었다.
먼저 SVG로 로고를 만들고, Framer Motion을 씌운 다음 useStateuseEffect로 로더를 보여주도록 구현했다.

로고

// components/Loader/Alphabets.tsx

import { motion } from 'framer-motion';
import { draw } from '@utils/draw';

export const Alphabets = () => {
  return (
    <motion.svg
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth="20px"
      viewBox="0 0 1000 1000"
      initial="hidden"
      animate="visible"
      className="m-auto block h-full w-full"
    >
      <motion.circle cx="250" cy="500" r="70" stroke="#0099ff" fill="none" variants={draw} custom={1} className="stroke-slate-800 dark:stroke-slate-200" />
      <motion.line x1="360" y1="430" x2="360" y2="570" stroke="#0099ff" variants={draw} custom={2.2} className="stroke-slate-800 dark:stroke-slate-200" />
      <motion.path
        d="M560 340 L420 385 L560 430 L420 475 L560 520"
        stroke="#0099ff"
        variants={draw}
        custom={2}
        fill="none"
        className="stroke-slate-800 dark:stroke-slate-200"
      />
      <motion.circle cx="490" cy="630" r="70" stroke="#0099ff" fill="none" variants={draw} custom={1} className="stroke-slate-800 dark:stroke-slate-200" />
      <motion.circle cx="680" cy="480" r="70" stroke="#0099ff" fill="none" variants={draw} custom={1} className="stroke-slate-800 dark:stroke-slate-200" />
      <motion.path
        d="M700 490 L840 490 L700 630 L840 630"
        stroke="#0099ff"
        variants={draw}
        custom={2}
        fill="none"
        className="stroke-slate-800 dark:stroke-slate-200"
      />
    </motion.svg>
  );
};

로고는 노가다의 산물이다...

애니메이션

애니메이션을 위한 Variants는 따로 utils로 빼줬다.

// utils/draw.ts

import { Variants } from "framer-motion";

export const draw: Variants = {
  hidden: { pathLength: 0, opacity: 0 },
  visible: (i: number) => {
    const delay = 1 + i * 0.5;
    return {
      pathLength: 1,
      opacity: 1,
      transition: {
        pathLength: { delay, type: 'spring', duration: 1.5, bounce: 0 },
        opacity: { delay, duration: 0.01 },
      },
    };
  },
};

로더

로더로 SVG를 감싸 페이지 가운데에 예쁘게 표시되도록 하고, 로더를 index.tsx에서 쉽게 컨트롤할 수 있게 별도의 컴포넌트로 만들었다.

// components/Loader.tsx

import { motion } from 'framer-motion';
import { Alphabets } from './Alphabets';

export const Loader = () => {
  return (
    <motion.div
      className="fixed z-50 h-[100vh] w-[100vw] bg-slate-200 dark:bg-slate-800"
      initial={{ opacity: 1 }}
      animate={{ opacity: 0 }}
      transition={{
        duration: 0.5,
        delay: 3,
      }}
    >
      <div className="mx-auto h-[100vh] w-max max-w-[500px]">
        <Alphabets />
      </div>
    </motion.div>
  );
};

index.tsx

isVisible을 처음에 true로 설정하고, 애니메이션 길이만큼의 시간 이후에 사라지도록 useEffect에서 setTimeout을 사용했다.

// pages/index.tsx

/* ... */
  const [isVisible, setIsVisible] = useState(true);

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      setIsVisible(false);
    }, 5000);

    return () => {
      clearTimeout(timeoutId);
    };
  }, []);
/* ... */
  return (
    <>
      <Head>
        <title>olwooz&apos;s portfolio</title>
        <meta name="description" content="olwooz's portfolio" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className={darkMode ? 'dark bg-slate-800' : 'bg-slate-200'}>
        {isVisible && <Loader />}
        <Header />
        <Main />
        <About />
        <Projects />
        <Contact />
        <IconBar direction="left" icons={leftIcons} />
        <IconBar direction="right" icons={rightIcons} />
      </div>
    </>
  );
}

결과

지금은 애니메이션이 단순한 편이지만, 추후에 애니메이션을 고도화해도 재밌을 것 같다.

0개의 댓글