화면에 고정되는 메인메뉴 만들기 - 리액트, 자바스크립트

화면에 고정되는 메인메뉴 만들기 - 리액트, 자바스크립트

리액트(React)를 활용하여 화면 상단에 고정되는 메뉴를 만드는 방법입니다. 스크롤을 내리면 타이틀이 사라지면서 메뉴가 화면 상단에 고정됩니다. 같은 기능을 구현할 때 자바스크립트만 사용하면 꽤 복잡한 코딩을 해야 합니다. 간단하고 빠르게 원하는 기능을 구현할 수 있다는 점은 우리가 리액트를 사용하는 이유 중 하나겠죠.

그럼 스크롤을 내렸을 때 메뉴를 화면 상단에 고정하는 방법에 대해서 알아보겠습니다.

1. 기본 기능 – App 작성

스크롤을 내렸을 때 메뉴를 상단에 고정하는 기능을 구현하려면 스크롤 이벤트를 감지하고 메뉴의 스타일과 타이틀 스타일을 동적으로 조작해야 합니다. useEffect를 이용하면 간단하게 처리할 수 있습니다.

다음은 useEffect와 useState를 사용하여 기능을 구현하는 예제 코드입니다.

import React, { useState, useEffect } from 'react';

function App() {
  const [isSticky, setSticky] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      // 현재 스크롤 위치를 가져옴
      const currentScrollPosition = window.pageYOffset;
      
      // 스크롤 위치가 메뉴의 위치보다 크거나 같으면 메뉴를 고정
      if (currentScrollPosition >= 50) {
        setSticky(true);
      } else {
        setSticky(false);
      }
    };

    window.addEventListener('scroll', handleScroll);

    // Clean up function
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return (
    <div>
      <header className={isSticky ? 'sticky' : ''}>
        {isSticky ? null : <h1>Title</h1>}
        <nav>
          <ul>
            <li>Menu 1</li>
            <li>Menu 2</li>
            <li>Menu 3</li>
          </ul>
        </nav>
      </header>
      <div>
        {/* 나머지 페이지 콘텐츠 */}
      </div>
    </div>
  );
}

export default App;

위 코드에서는 useState를 사용하여 isSticky 상태를 관리하고, useEffect를 사용하여 스크롤 이벤트를 수신합니다. useEffect의 두 번째 인자로 빈 배열을 전달하여 컴포넌트가 마운트될 때만 이벤트 핸들러가 작동되도록 해줍니다.

handleScroll 함수에서는 현재 스크롤 위치를 가져와 메뉴의 위치와 비교한 후, setSticky 함수를 호출하여 isSticky 상태를 변경합니다. return 구문에서는 컴포넌트가 언마운트될 때 이벤트 핸들러를 제거합니다.

마지막으로, header 요소에 sticky 클래스를 동적으로 추가하여 CSS를 통해 메뉴를 고정합니다. 이제 스크롤을 내리면 sticky 클래스가 해당 요소에 추가되는 것을 확인할 수 있을 것입니다.

위 코드는 스크롤을 내렸을 때 타이틀을 숨겨줍니다. 만약 스크롤을 내렸을 때 타이틀을 그대로 보여주고 싶다면 아래 부분처럼 수정해주면 됩니다.

      ...
      <header className={isSticky ? 'sticky' : ''}>
        <h1>Title</h1>
        <nav>
        ...
      ...

위와 같이 코드를 수정하면 타이틀이 사라지지 않습니다.

2. CSS 작성

무엇인가를 화면에 고정할 때 사용할 수 있는 CSS 속성은 position: fixed입니다. 아래 CSS 속성들은 기본적인 디자인만 구현되어 있으며, 필요한 것들은 추가해서 사용할 수 있습니다.

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  position: relative;
}

.sticky {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  background-color: #fff;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  z-index: 100;
}

header 요소에는 display: flex; 속성을 적용하여 내부 요소들을 가로 방향으로 정렬합니다. 그리고 justify-content: space-between; 속성을 적용하여 내부 요소들 사이의 간격을 균등하게 설정합니다. align-items: center; 속성을 적용하여 내부 요소들을 수직 중앙 정렬합니다. padding: 1rem; 속성을 적용하여 header 요소의 마진을 설정합니다. position: relative; 속성을 적용하여 내부 요소들이 상대적인 위치를 가지도록 설정합니다.

sticky 클래스에는 position: fixed; 속성을 적용하여 요소를 화면 상단에 고정시킵니다. top: 0; 속성을 적용하여 요소를 화면 상단에 정확히 위치시킵니다. left: 0; 속성을 적용하여 요소를 화면 왼쪽에 위치시킵니다. width: 100%; 속성을 적용하여 요소의 너비를 화면 전체로 설정합니다. background-color: #fff; 속성을 적용하여 요소의 배경색을 흰색으로 설정합니다. box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 속성을 적용하여 요소에 그림자 효과를 추가합니다. z-index: 100; 속성을 적용하여 요소의 z-index 값을 100으로 설정합니다.

위와 같이 sticky 클래스를 적용하면, 요소가 화면을 스크롤할 때 일정 위치에 고정되어 화면 상단에 고정된 메뉴를 구현할 수 있습니다.

3. 애니메이션 적용

타이틀이 사라질 때 애니메이션을 적용하여 위쪽으로 부드럽게 이동하도록 할 수도 있습니다. 리액트에서는 애니메이션을 비교적 쉽게 다룰 수 있는데, 이 코드에서 타이틀이 사라질 때 위쪽으로 올라가는 애니메이션을 적용하기 위해서는 react-transition-group 패키지를 사용하여 CSS 트랜지션을 구현해야 합니다.

먼저 react-transition-group 패키지를 설치합니다.

npm install react-transition-group

다음은 react-transition-group 패키지를 사용하여 타이틀 숨김 애니메이션을 구현한 예시입니다.

import React, { useState, useEffect } from 'react';
import { CSSTransition } from 'react-transition-group';
import './styles.css';

function App() {
  const [isSticky, setSticky] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      // 현재 스크롤 위치를 가져옴
      const currentScrollPosition = window.pageYOffset;
      
      // 스크롤 위치가 메뉴의 위치보다 크거나 같으면 메뉴를 고정
      if (currentScrollPosition >= 50) {
        setSticky(true);
      } else {
        setSticky(false);
      }
    };

    window.addEventListener('scroll', handleScroll);

    // Clean up function
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return (
    <div>
      <header className={isSticky ? 'sticky' : ''}>
        <CSSTransition
          in={!isSticky}
          timeout={300}
          classNames="title"
          unmountOnExit
        >
          <h1>Title</h1>
        </CSSTransition>
        <nav>
          <ul>
            <li>Menu 1</li>
            <li>Menu 2</li>
            <li>Menu 3</li>
          </ul>
        </nav>
      </header>
      <div>
        {/* 나머지 페이지 콘텐츠 */}
      </div>
    </div>
  );
}

export default App;

위 코드에서는 CSSTransition 컴포넌트를 사용하여 타이틀 숨김 애니메이션을 구현합니다. in prop을 사용하여 타이틀이 보여지는지 숨겨져 있는지를 결정하고, timeout prop을 사용하여 애니메이션 지속 시간을 설정합니다. classNames prop을 사용하여 CSS 클래스를 설정합니다. unmountOnExit prop을 사용하여 애니메이션 종료 후 DOM에서 컴포넌트를 제거합니다.

CSSTransition 컴포넌트를 사용하여 타이틀 숨김 애니메이션을 구현하도록 예시 코드를 작성하였습니다. 이제 CSS 파일에서 애니메이션을 구현합니다.

.title-enter {
  opacity: 0;
  transform: translateY(-100%);
}

.title-enter-active {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 300ms ease-in-out, transform 300ms ease-in-out;
}

.title-exit {
  opacity: 1;
  transform: translateY(0);
}

.title-exit-active {
  opacity: 0;
  transform: translateY(-100%);
  transition: opacity 300ms ease-in-out, transform 300ms ease-in-out;
}

먼저, title-enter 스타일을 설정하여 컴포넌트가 처음 마운트될 때의 스타일을 정의합니다. opacity 속성을 0으로 설정하여 컴포넌트를 숨기고, transform 속성을 translateY(-100%)으로 설정하여 컴포넌트를 화면 위쪽으로 이동시킵니다.

다음으로, title-enter-active 스타일을 설정하여 애니메이션이 실행 중일 때의 스타일을 설정합니다. opacity 속성을 1로 설정하여 컴포넌트를 보이도록 하고, transform 속성을 translateY(0)으로 설정하여 컴포넌트를 원래 위치로 이동시킵니다. transition 속성을 사용하여 애니메이션의 지속 시간과 타이밍을 설정합니다.

타이틀이 사라질 때 적용되는 title-exit와 title-exit-active 스타일도 title-enter-* 스타일과 같은 방법으로 작성합니다.

이제 스크롤을 내리면 메뉴가 화면 상단에 고정되면서 타이틀이 애니메이션과 함께 화면 위쪽으로 사라지는 것을 확인할 수 있습니다.

4. 자바스크립트만 사용하여 작성하기

리액트를 사용하지 않고 자바스크립트만 사용해서 같은 기능을 구현할 수도 있습니다. 리액트보다 간단하게 보이지만, 직접 DOM을 조작해야 하고, 유지보수가 힘들어서 가능하다면 리액트로 작성하는 것이 좋습니다.

const header = document.querySelector('header');
const h1 = header.querySelector('h1');
const nav = header.querySelector('nav');
const ul = nav.querySelector('ul');

function handleScroll() {
  // 현재 스크롤 위치를 가져옴
  const currentScrollPosition = window.pageYOffset;
  
  // 스크롤 위치가 메뉴의 위치보다 크거나 같으면 메뉴를 고정
  if (currentScrollPosition >= 50) {
    header.classList.add('sticky');
    h1.style.display = 'none';
    ul.style.marginTop = '0';
  } else {
    header.classList.remove('sticky');
    h1.style.display = 'block';
    ul.style.marginTop = '';
  }
}

window.addEventListener('scroll', handleScroll);
<header>
  <h1>Title</h1>
  <nav>
    <ul>
      <li>Menu 1</li>
      <li>Menu 2</li>
      <li>Menu 3</li>
    </ul>
  </nav>
</header>
<div>
  <!-- 나머지 페이지 콘텐츠 -->
</div>

위 코드에서는 querySelector 메서드를 사용하여 DOM 요소를 가져옵니다. handleScroll 함수에서는 현재 스크롤 위치를 가져온 후, 스크롤 위치에 따라 요소들의 스타일을 조작합니다.

header.classList.add(‘sticky’) 코드는 header 요소에 sticky 클래스를 추가하여 메뉴를 고정합니다. h1.style.display = ‘none’ 코드는 h1 요소의 스타일을 직접 조작함으로써 화면에서 숨깁니다. ul.style.marginTop = ‘0’ 코드는 ul 요소의 margin-top 값을 0으로 설정하여 메뉴가 화면 상단에 고정될 때 메뉴와 페이지 콘텐츠 사이의 간격을 없앱니다.

header.classList.remove(‘sticky’) 코드는 header 요소에서 sticky 클래스를 제거하여 메뉴를 원래 위치로 돌려놓습니다. h1.style.display = ‘block’ 코드는 h1 요소를 다시 보이도록 합니다. ul.style.marginTop = ” 코드는 ul 요소의 margin-top 값을 초기값으로 되돌려줍니다.

위와 같이 자바스크립트로도 같은 기능을 구현할 수 있습니다. 다만, 리액트를 사용하는 것보다는 코드가 복잡해지고 유지보수가 어려워질 수 있습니다.

Leave a reply

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다