[REACT] 스크롤 진행바(scroll progress bar)

포우·2023년 3월 4일

React

목록 보기
1/4


스크롤바가 내려감에 따라서 그 진행정도를 나타내는 scroll progress bar를 만들어보도록 하겠습니다.

크롬브라우저 환경에서 진행했습니다.

1. 준비단계
creat-react-app, vite, nextJS등을 사용하여 리액트 프로젝트를 생성해 줍니다.
저는 vite를 사용하겠습니다.
타입스크립트 없어도 되지만 저는 사용하겠습니다.

npm create vite@latest

스타일링 단순화를 위해서 App.css는 지우고 index.css만 사용하겠습니다.
App.tsx 파일에 스크롤을 만들기 위해 height를 지정해줍니다.

(App.tsx)
export default function App() {
  return (
    <div className='scroll'>
      <h1>hello world</h1>
    </div>
  );
}
(index.css)
.scroll {
  height: 5000px;
}

높이 5000px의 화면이 생성되었습니다.

2. 구상단계
진행정도를 0부터 100이라고 했을 때
0은 스크롤바가 가장 위에 있을 때
100은 스크롤바가 가장 아래에 있을 때를 의미 합니다.

그렇다면 다음과 같은 식이 성립하겠네요.
진행정도 = (현재 스크롤의 위치 / 맨위부터 맨아래까지의 스크롤 높이) * 100
이를 변수로 정의하면 다음과 같습니다

// 현재 스크롤의 위치
const currentScrollPosition = window.scrollY

// 맨위부터 맨아래까지의 스크롤 높이
const scrollHeight = document.body.scrollHeight

// 진행정도
const progress = ( currentScrollPosition / scrollHeight ) * 100

이렇게 되겠군요

3. 확인단계
useEffect를 이용해서 scroll에 event listener를 달아주겠습니다.

(App.tsx)
 import { useEffect } from "react";

export default function App() {
  useEffect(() => {
    const updateProgress = () => {
      const currentScrollPosition = window.scrollY;
      const scrollHeight = document.body.scrollHeight;
      const progress = ( currentScrollPosition / scrollHeight ) * 100
      console.log({ currentScrollPosition, scrollHeight, progress });
    };
    window.addEventListener("scroll", updateProgress);
  }, []);
  
  return (
    <div className='scroll'>
      <h1>hello world</h1>
    </div>
  );
}

scroll event 발생시 콘솔에 currentScrollPosition, scrollHeight, progress 찍어주는 코드입니다.

스크롤이 맨위에 있을 때 입니다.

scrollHeight는 5000px지정했으므로 5000이 나오고,
currentScrollPosition와 progress는 스크롤이 맨위에 있으므로 당연히 0이 나옵니다.

스크롤이 맨 아래에 있을 때 입니다.

예상과 다르게 currentScrollPosition이 5000이 아니라 4173.33이라는 숫자가 나왔네요.

이유는 다음과 같습니다.

currentScrollPosition의 기준은 회색바(사각형)의 최상단입니다.
사진에서 빨간선이 5000px에 해당하는 영역이며 회색바의 최상단이 움직이는 범위는 초록색 구간입니다.
따라서 빨간선에서 회색바만큼의 높이를 빼주면 되겠죠?

회색바가 나타내는 높이은 현재 브라우져의 안쪽 높이 입니다. 즉 window.innerHeght입니다.

따라서 scrollHeight 변수를 다음과 같이 바꾸겠습니다.

const scrollHeight = document.body.scrollHeight - window.innerHeight;

다시 콘솔을 확인해 보겠습니다
맨 아래로 내렸을 때의 상황입니다.

currentScrollPosiotion과 scrollHeight가 일치하지는 않지만 거의 유사한 값을 가집니다. 약간의 차이는 스크롤바에 위치하는 두개의 화살표의 높이 때문입니다. 정확하지는 않지만 30px 더해주면 거의 오차가 나지 않습니다.
브라우져에 따라 이 값은 약간씩 차이가 있습니다.

const scrollHeight = document.body.scrollHeight - window.innerHeight + 30;

progress의 소수점이 너무 길기 때문에 자연수로 만들겠습니다.

const progress = Number((currentScrollPosition / scrollHeight).toFixed(2)) * 100;

숫자.toFixed()를 하면 문자열 값이 나오기 때문에 중간에 숫자로 변환하는 작업이 필요합니다.

useState를 이용해서 progress를 scrollProgress 스테이트에 저장합니다.

import { useEffect, useState } from "react";

export default function App() {
  const [scrollProgress, setScrollProgress] = useState<number>(0);

  useEffect(() => {
    const updateProgress = (): void => {
      const currentScrollPosition = window.scrollY;
      const scrollHeight = document.body.scrollHeight - window.innerHeight + 30;
      const progress = Number((currentScrollPosition / scrollHeight).toFixed(2)) * 100;
      setScrollProgress(progress);
    };
    window.addEventListener("scroll", updateProgress);
  }, []);
  console.log(scrollProgress);

  return (
    <div className='scroll'>
      <h1>hello world</h1>
    </div>
  );
}

콘솔을 찍어보면 progress가 스크롤 위치에 따라 0부터 100까지 정수로 나타납니다.
이제 이 progress를 이용해서 스타일링만 하면 끝입니다.

4. 스타일링
css기본 스타일링은 초기화시켜주세요.

스크롤 바의 스타일링은 다음과 같습니다.

(App.tsx)
import { useEffect, useState } from "react";

export default function App() {
  const [scrollProgress, setScrollProgress] = useState<number>(0);

  useEffect(() => {
    const updateProgress = (): void => {
      const currentScrollPosition = window.scrollY;
      const scrollHeight = document.body.scrollHeight - window.innerHeight + 30;
      const progress =
        Number((currentScrollPosition / scrollHeight).toFixed(2)) * 100;
      setScrollProgress(progress);
    };
    window.addEventListener("scroll", updateProgress);
  }, []);
  console.log(scrollProgress);

  return (
    <div className='scroll'>
      <div className='progressbar' />
      <h1>hello world</h1>
    </div>
  );
}
(index.css)
.scroll {
  height: 5000px;
}

.progressbar {
  width: 100vw;
  height: 15px;
  background: #aaf;
  position: fixed;
}


width는 화면 너비로해주시고 position을 fixed로 줘서 스크롤을 내려도 그 위치에 고정이 되도록 합니다.

5. 움직임 구현
드디어 마지막 입니다.
보라색 progressbar가 progress의 값이 0일 때는 왼쪽에서 숨어 있다가
값이 증가함에 따라서 오른쪽으로 슬금슬금 기어나오면 됩니다.

<div
  className='progressbar'
  style={{ transform: `translateX(${scrollProgress - 100}%)` }}
/>

잘 작동합니다!!

6. 커스텀 훅
해당 로직은 한번밖에 사용되지만 useProgressbar라는 커스텀훅을 만들어서 App.tsx파일을 좀더 깔끔하게 다듬어볼게요.

(useProgressbar.ts)
import { useEffect, useState } from "react";

export const useProgressbar = (): number => {
  const [scrollProgress, setScrollProgress] = useState<number>(0);

  useEffect(() => {
    const updateProgress = (): void => {
      const currentScrollPosition = window.scrollY;
      const scrollHeight = document.body.scrollHeight - window.innerHeight + 30;
      const progress =
        Number((currentScrollPosition / scrollHeight).toFixed(2)) * 100;
      setScrollProgress(progress);
    };
    window.addEventListener("scroll", updateProgress);
  }, []);

  return scrollProgress;
};
(App.tsx)
import { useProgressbar } from "./useProgressbar";

export default function App() {
  const scrollProgress = useProgressbar();
  return (
    <div className='scroll'>
      <div
        className='progressbar'
        style={{ transform: `translateX(${scrollProgress - 100}%)` }}
      />
      <h1>hello world</h1>
    </div>
  );
}

긴 글 읽어주셔서 감사합니다!😊😊

profile
개발바닥

0개의 댓글