createContext를 사용하여 provider, context 생성

Toc.js 생성

메뉴에서 이동 시킬 위치를 각각의 컴포넌트에서 pos로 받아와서 저장 시킬 예정

import React from "react";
import { createContext, useState } from "react";

const ToCContext = createContext([{}, () => {}]);
const TocProvider = ({ children }) => {
  const [pos, setPos] = useState({
    First: 0,
    Second: 0,
    Third: 0,
    Fourth: 0,
  });

  return (
    <ToCContext.Provider value={[pos, setPos]}>{children}</ToCContext.Provider>
  );
};
const { Consumer: ToCConsumer } = ToCContext;
export { TocProvider, ToCConsumer };
export default ToCContext;

페이지의 위치 pos에 저장시키기

먼저, context를 사용하려는 컴포넌트 바깥에서 Provider로 감싸준다.

import logo from "./logo.svg";
import "./App.css";
import Nav from "./Nav";
import First from "./First";
import Second from "./Second";
import Third from "./Third";
import Fourth from "./Fourth";
import { TocProvider } from "./Toc";
import styled from "styled-components";
const BodyStyle = styled.div``;
function App() {
  return (
    <div className="App">
      <TocProvider>
        <Nav />
        <BodyStyle>
          <First />
          <Second />
          <Third />
          <Fourth />
        </BodyStyle>
      </TocProvider>
    </div>
  );
}

export default App;

 

ToC.js에서 생성한 ToCContext의 pos를 useContext로 불러옴

useRef를 이용해서 컴포넌트 First의 ref값을 가져옴

해당 컴포넌트의 절대값을 구하기위해  Viewport의 시작지점을 기준으로 한 상대 좌표인 element.getBoundingClientRect(). top과 window.pageYOffset 전체 페이지에서 얼만큼 scroll 됐는지 픽셀 단위를 알려주는 값을 더 함

import React, { useContext, useEffect, useRef } from "react";
import styled from "styled-components";
import ToCContext from "./Toc";
const FirstStyle = styled.div`
  width: 100%;
  height: 500px;
  background-color: green;
`;
function First() {
  const element = useRef(null);
  const [pos, setPos] = useContext(ToCContext);
  useEffect(() => {
    const rect = element.current.getBoundingClientRect();
    setPos((pos) => ({
      ...pos,
      [element.current.id]: rect.top + window.pageYOffset,
    }));
  }, [setPos]);
  return (
    <FirstStyle id="First" ref={element}>
      first
    </FirstStyle>
  );
}

export default First;

네비게이션 구현

useEffect로 현재 스크롤 위치가 메뉴에 해당하는 컴포넌트 영역에 들어왔는지 체크 하고 들어오면 메뉴 폰트에 bold를 줌

import React, { useContext, useEffect, useState } from "react";
import styled from "styled-components";
import ToCContext from "../../../components/ToC";

const NavFont = styled.a`
  font-family: Noto Sans KR;
  font-weight: ${(props) => props.active};
  font-size: 2.2rem;
  color: #ffffff;
  line-height: 3.2rem;
  text-decoration: none;
`;
function Header({
  list = [
    {
      name: "First",
      href: "First",
    },
    {
      name: "Second",
      href: "Second",
    },
    {
      name: "Third",
      href: "Third",
    },
    {
      name: "Fourth",
      href: "Fourth",
    },
  ],
}) {
  const [active, setActive] = useState(0);
  const [pos] = useContext(ToCContext);

  useEffect(() => {
    const onScroll = () => {
      const scrollTop = window.pageYOffset;
      if (scrollTop < pos.Second) {
        setActive(0);
      }
      if (pos.Second <= scrollTop && scrollTop < pos.Third) {
        setActive(1);
      }
      if (pos.Third <= scrollTop && scrollTop < pos.Fourth) {
        setActive(2);
      }
      if (pos.Fourth <= scrollTop) {
        setActive(3);
      }
    };
    if (pos.Second !== 0 && pos.Thrid !== 0 && pos.Fourth !== 0) {
      window.addEventListener("scroll", onScroll);
    }
    console.log("active", active);
    return () => {
      window.removeEventListener("scroll", onScroll);
    };
  }, [pos]);

  return (
    <Box>
      <NavBox>
        {list.map((el, idx) => (
          <NavFont
          key={idx}
          href={`#${el.href}`}
          active={idx === active ? "bold" : "regular"}
          >
          {el.name}
          </NavFont>
        ))}
      </NavBox>
    </Box>
  );
}

export default Header;
728x90

+ Recent posts