기능 | 스플래시 화면 - ssseok/wedding.invitation GitHub Wiki

스플래시 화면 이란?

  • 앱을 실행시켰을 때 가장 처음으로 만나는 화면
  • 앱의 첫인상을 결정하는 브랜드 아이덴티티를 표현할 수 있는 공간
  • 런치 스크린(Launch, Launcher Screen)이라고도 합니다

목적

  • (시각적 즐거움보다) 사용자의 첫인상을 설계하고, 첫 방문 혹은 재방문 등 각각의 타입에 따라 다른 랜딩 화면을 제공하기 위한 준비 화면
  • 유저들이 앱을 계속해서 이용할지 말지 결정하는 이유 중 하나가 되는 UI요소
  • 사용자가 어떤 서비스인지 정확하게 모르는 상황에서, 전체 분위기를 가늠해 볼 수 있는 간판
  • 앱 실행시마다 반복적으로 노출되므로 브랜딩의 일환으로 사용

특징

  • 짧게는 1초에서 5초 정도 노출. 보통 3초까지가 최대 (길어지면 사용자 이탈 가능성이 높음)
  • 짧은 시간 내 앱의 이미지를 심어줍니다.

코드

import { memo, useLayoutEffect, useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { SPLASH_TEXT } from '../../config';

interface SplashScreenProps {
  fadeOutTime?: number;
}

const splashScreenElement = document.getElementById(
  'splash-screen',
) as HTMLElement;

function SplashScreen({
  fadeOutTime = 3000,
}: SplashScreenProps): JSX.Element | null {
  const [currentText, setCurrentText] = useState('');
  const [textIndex, setTextIndex] = useState(0);

  useEffect(() => {
    if (currentText.length < SPLASH_TEXT[textIndex].length) {
      const timer = setTimeout(() => {
        setCurrentText(
          (prev) => prev + SPLASH_TEXT[textIndex][currentText.length],
        );
      }, 50);

      return () => clearTimeout(timer);
    } else {
      const timer = setTimeout(() => {
        if (textIndex < SPLASH_TEXT.length - 1) {
          setTextIndex((prev) => prev + 1);
          setCurrentText('');
        }
      }, 1000);

      return () => clearTimeout(timer);
    }
  }, [currentText, textIndex]);

  useLayoutEffect(() => {
    if (!splashScreenElement) return;

    const clearId = setTimeout(() => {
      splashScreenElement.innerHTML = '';
    }, fadeOutTime);

    return () => {
      clearTimeout(clearId);
    };
  }, [fadeOutTime]);

  if (!splashScreenElement) return null;

  return createPortal(
    <div className='fixed inset-0 z-50 backdrop-blur-sm bg-foreground/50 max-w-screen-sm mx-auto w-full h-full bg-cover bg-center bg-no-repeat animate-fade-out opacity-100 delay-3000'>
      <div className='absolute inset-0 flex flex-col items-center justify-center mx-auto gap-4'>
        <Heart />
        <div className='text-background text-xl min-h-[2rem]'>
          {currentText}
          <span className='animate-pulse'>|</span>
        </div>
      </div>
    </div>,
    splashScreenElement,
  );
}

export default memo(SplashScreen);

function Heart() {
  return (
    <svg
      version='1.1'
      xmlns='http://www.w3.org/2000/svg'
      className='w-[60px] h-[60px] fill-red-500 animate-heart-beat'
      viewBox='-2 -2 28 28'
    >
      <path d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z' />
    </svg>
  );
}
  1. createPortal를 사용한 이유
  • createPortal은 React의 createPortal은 컴포넌트를 DOM 트리의 다른 위치에 렌더링할 수 있게 해줍니다.
  • 스플래시 화면 같은 경우 다른 컴포넌트들과 다르게 독립적으로 존재해야합니다.
  • 스플래시 화면은 최상위 레벨에 위치하여 전체 화면을 덮어야하기 때문입니다.
  • index.html에 id="splash-screen"인 별도의 DOM 요소에 렌더링함으로써 메인 애플리케이션 컴포넌트 트리와 분리됩니다.
<body>
    <div id="splash-screen"></div>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
</body>
  • 이렇게 하면 스플래시 화면의 스타일링과 애니메이션이 다른 컴포넌트에 영향을 주지 않습니다.
  1. memo를 사용한 이유
  • memo는 컴포넌트의 불필요한 리렌더링을 방지합니다.
  • 스플래시 화면은 부모 컴포넌트가 리렌더링될 때마다 다시 렌더링될 필요가 없습니다.
  • 스플래시 화면의 props(fadeOutTime)가 변경되지 않는 한, 컴포넌트를 다시 렌더링할 필요가 없습니다.
  • 성능 최적화를 위해 사용하였습니다.
  1. useLayoutEffect를 사용한 이유
  • useLayoutEffect는 DOM 변경 후 브라우저가 화면을 그리기 전에 동기적으로 실행됩니다.
  • 스플래시 화면을 제거하는 작업은 시각적인 깜빡임 없이 부드럽게 이루어져야 합니다.
  • useEffect와 달리 useLayoutEffect는 브라우저가 화면을 그리기 전에 실행되므로, 스플래시 화면 제거 시 시각적 깜빡임을 방지합니다.
  • splashScreenElement.innerHTML = ''를 통해 스플래시 화면을 DOM에서 직접 제거하는 방식을 사용했습니다.
  1. useStateuseEffect를 활용한 타이핑 효과
  • 타이핑 효과를 구현하여 텍스트가 한 글자씩 나타나는 애니메이션을 만듭니다.
  • currentText 상태는 현재까지 표시된 텍스트를 저장합니다.
  • textIndex 상태는 현재 표시 중인 텍스트의 인덱스를 추적합니다.
  • useEffect에서 50ms마다 한 글자씩 추가하는 타이머를 설정합니다.
  • 텍스트가 모두 표시되면 1초 후에 다음 텍스트로 넘어갑니다.
  • 모든 텍스트가 표시되면 타이핑 효과가 종료됩니다.

참고

https://ko.react.dev/reference/react-dom/createPortal

https://xionwcfm.tistory.com/316

⚠️ **GitHub.com Fallback** ⚠️