[React, JS] 원페이지 스크롤 구현하기

Sungho Kim·2022년 10월 31일
1

시작에 앞서,

랜딩페이지에서 원페이지 스크롤을 구현하고 싶었다. 구글링을 해봤지만.. npm 패키지는 유료버전밖에 없었고, 아무리 서칭을 해봐도 javaScript와 리액트로만 구현된 원페이지 스크롤 기능이 없었다. jQuery로 구현된 원페이지 스크롤은 여러개가 있었지만 jQuery로는 구현하고 싶지 않은 나만의 고집이 있어서 자바스크립트를 공부할겸 차근차근 나만의 라이브러리를 만들어보려 한다.

사용된 이벤트 & 함수

Web Apis

window.innerHeight

// 현재 페이지의 윈도우 창 크기를 알아내는 이벤트
window.scrollY
// 현재 스크롤이 어디를 지나고 있는지 알려주는 이벤트
window.scrollTo(x좌표, y좌표)
// 기준: 문서의 왼쪽상단
// x좌표 : 가로축 , y좌표 : 세로축

window.scrollTo({top: y, left: x, behavior: "smooth"})
// x좌표 : 가로축 px , y좌표 : 세로축px
// behavior: smooth 매끄럽게 내려가는 기능

함수

window.addEventListener("wheel", (e) =>{
	e.deltaY
}

// 스크롤을 내리는지 올리는지 알아내는 이벤트
// 음수일 경우 마우스가 올라가는 중이라는 것
// 양수일 경우 마우스가 내려가는 중이라는 것
window.addEventListener("resize", () =>{
	setWindowSize(window.innerHeight)
}

// 유저가 화면의 크기를 바꾸거나 할때, 한페이지를 다시 정의할때 사용되는 함수

로직을 먼저 생각해보자

내가 새운 로직은 이렇다.
1. home 안에 위치한 div블록 숫자를 알아낸다.
2. home에 위치한 전체 윈도우 height을 알아낸다.
3. 스크롤을 내릴때와 올릴때 이벤트를 알아내서
4. 스크롤을 내릴때 현재 위치가 블록 위치보다 낮다면 윈도우 창 크기만큼 스크롤을 다운하고
5. 스크롤을 올릴때 현재 위치가 블록 위치보다 높다면 윈도우 창 크기만큼 스크롤을 업한다

소스코드

import styled from "styled-components";
import { useEffect, useState } from "react";

const Base = styled.div`
  width: 100%;
  height: 100vh;
`;

const SliderObject = styled.div`
  width: 100vw;
  height: 100vh;
  overflow: hidden;
`;

const TomatoBox = styled(SliderObject)`
  background-color: lightcoral;
`;

const BlueBox = styled(SliderObject)`
  background-color: skyblue;
`;

const GreenBox = styled(SliderObject)`
  background-color: lightgreen;
`;

const GreyBox = styled(SliderObject)`
  background-color: grey;
`;

const OrangeBox = styled(SliderObject)`
  background-color: orange;
`;

function HomeWeb() {
  const wholePage = document.getElementsByClassName("slider");
  const totalPageNumber = wholePage[0]?.children?.length;

  const [currentInputs, setCurrentInputs] = useState({
    currentWindowHeight: window.innerHeight,
    currentPage: 0,
  });

  //윈도우 리사이즈 시, 윈도우 사이즈를 조정한다
  const setPageSize = () => {
    setCurrentInputs({ currentWindowHeight: window.innerHeight });
  };

  //현재 페이지가 몇페이지인지 구하는 함수
  const setPage = () => {
    for (var i = 1; i < totalPageNumber; i++) {
      if (window.scrollY < currentInputs.currentWindowHeight * i) {
        setCurrentInputs({ ...currentInputs, currentPage: i });
        return;
      }
    }
  };
  
  // Scroll Event와 Resize시 무한 반복을 피하기 위함
  useEffect(() => {
    window.addEventListener("scroll", setPage);
    window.addEventListener("resize", setPageSize);
    return () => {
      window.removeEventListener("scroll", setPage);
      window.removeEventListener("resize", setPageSize);
    };
  });

  window.addEventListener("wheel", (e) => {
    // 마우스 휠을 내릴때
    if (e.deltaY > 0) {
      let p = 1;
      while (p < totalPageNumber) {
        if (currentInputs.currentPage === p) {
          window.scrollTo({
            top: currentInputs.currentWindowHeight * p,
            behavior: "smooth",
          });
        }
        p++;
      }
    }
    // 마우스 휠을 올릴때
    if (e.deltaY < 0) {
      let p = 1;
      while (p < totalPageNumber) {
        if (currentInputs.currentPage === p) {
          window.scrollTo({
            top: currentInputs.currentWindowHeight * (p - 1),
            behavior: "smooth",
          });
        }
        p++;
      }
    }
  });

  return (
    <Base className="slider">
      <SliderObject>
        <TomatoBox></TomatoBox>
      </SliderObject>
      <SliderObject>
        <BlueBox></BlueBox>
      </SliderObject>
      <SliderObject>
        <GreenBox></GreenBox>
      </SliderObject>
      <SliderObject>
        <GreyBox></GreyBox>
      </SliderObject>
      <SliderObject>
        <OrangeBox></OrangeBox>
      </SliderObject>
    </Base>
  );
}

export default HomeWeb;
profile
공유하고 나누는걸 좋아하는 개발자

1개의 댓글

comment-user-thumbnail
2023년 9월 22일

당신은 신입니까?

답글 달기