[React] [웹 접근성] createPortal을 사용한 SplashScreen

Joowon Jang·2024년 11월 7일
1

React

목록 보기
8/19
post-thumbnail

Splash Screen

애플리케이션 실행 시 페이지의 컨텐츠가 로딩되기까지 일시적으로 보여주는 화면을 말한다. 모바일 애플리케이션의 경우 로고가 중앙에 박혀있는 화면을 가장 많이 사용한다.

구현하기

아래의 gif를 보면, 앱 실행 시 splash-screen 요소가 렌더링되고 사라지면서 HTML 요소도 함께 변경되는 것을 볼 수 있다.
추가로, splash-screen과는 별도로 앱의 콘텐츠가 로드됨에 따라 아래쪽의 loading-start, loading-end 요소에서 로딩 시작과 끝을 알려준다.

우선, 아래와 같이 리액트 앱 외부에서 SplashScreen을 렌더링할 div를 만들어준다.
Splash Screen은 앱의 다른 요소와는 독립적으로 동작해야 하므로, 별도의 DOM 노드에 렌더링하여 레이아웃 관리가 용이하도록 했다.

<!-- index.html -->

<!doctype html>
<html lang="ko-KR">
  <head>
    <meta charset="UTF-8" />
    <title>해마디</title>
    <!-- ... -->
    <script type="module" src="src/main.jsx"></script>
  </head>

  <body>
    <noscript>이 앱을 사용하려면 JavaScript 활성화가 필요합니다.</noscript>

    <div id="splash-screen"></div>

    <div id="root"></div>
  </body>
</html>

그 후에 App.jsx에 SplashScreen 컴포넌트를 렌더링하도록 추가해준다.
이 때, 경로에 따라 렌더링하는 콘텐츠를 변경하는 Router와는 분리되어야 하기 때문에, 외부에 넣어준다.
(별도의 SEO를 제공하지도 않기 때문에 HelmetProvider에도 포함하지 않았다.)

// App.jsx

import { HelmetProvider } from 'react-helmet-async';
import SplashScreen from './components/SplashScreen/SplashScreen';
import AppRouter from './router';

function App() {
  return (
    <>
      <SplashScreen />
      <HelmetProvider>
        <AppRouter />
      </HelmetProvider>
    </>
  );
}

export default App;

아래는 SplashScreen 컴포넌트이고, 다음의 순서대로 렌더링된다.
1. index.html에 존재하는 splash-screen 요소를 참조하고 있다.
2. 앱이 실행되면, createPortal을 통해 splash-screen 요소에 이미지와 제목 등을 렌더링한다. (화면에 보여지는 SplashScreen 뒤에서는 콘텐츠 로드가 이루어지는 중)
3. useLayoutEffect를 사용해 2초 뒤에 splash-screen의 HTML 코드를 비우라는 타이머를 설정한다.
4. 2초 뒤에 SplashScreen의 콘텐츠는 사라지고 로딩 스피너 혹은 로딩이 완료된 콘텐츠가 보인다.

// SplashScreen.jsx

import { memo, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';
import styles from './SplashScreen.module.css';

// splash-screen 요소
// 컴포넌트 초기화 과정에서 1회만 필요하므로 컴포넌트 외부에서 참조
const splashScreenElement = document.getElementById('splash-screen');

// SplashScreen 컴포넌트
function SplashScreen({ fadeOutTime = 2000 }) {
  // 레이아웃 이펙트
  useLayoutEffect(() => {
    // 페이드 아웃 타임이 되면 스플래시 스크린 콘텐츠 삭제
    const clearId = setTimeout(() => {
      splashScreenElement.innerHTML = '';
    }, fadeOutTime);

    return () => {
      // 설정된 타이머 정리
      clearTimeout(clearId);
    };
  }, [fadeOutTime]);

  // ReactDOM의 포털을 사용해 splash-screen 요소에 렌더링
  // 스플래시 스크린의 적절한 제목 설정 필요
  return createPortal(
    <div className={styles.splashScreen}>
      <div className={styles.textBox}>
        <p>감정 관리를 위한 일기 서비스</p>
        <h1>해마디</h1>
      </div>
      // ...
    </div>,
    splashScreenElement
  );
}

// 컴포넌트 속성이 변경되지 않을 경우 불필요한 리-렌더링 차단
export default memo(SplashScreen);

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

profile
깊이 공부하는 웹개발자

0개의 댓글