[포트폴리오]네비바와 타이틀 컴포넌트

Yeong·2023년 12월 6일
0

프로젝트

목록 보기
3/4

overflow-hidden 적용 안 됨

구름이 배경 넘어로 튀어나오는 현상은 배경도 positon을 relative로 변경했더니 들어갔다. overflow-hidden는 부모 자식 같이 static이거나 아니면 둘 다 static 아니여야 적용이 되는 것 같다.

tailwind 이미지 경로

처음에 연습 삼아 index.css에 작성해서 tailwind로 수정했는데 이미지 파일을 계속 인식 못하는 에러 발생

./scr/index.css에서 에러나 난 이유는 css에서 절대 경로가 src폴더 이기 때문이다. 현재 경로에서 /image/cloud.png를 찾아야 하므로 ./image/cloud.png 으로 고쳐주면 에러가 사라진다.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        'hero-pattern': "url('./image/cloud.png')"
      }
    },
  },
  plugins: [],
}

h1 숨김처리

section에는 h1이 있어야 웹표준에 좋지만 h1이 화면에 필요 없다면 숨김처리 해두면 좋다고 해서 h1을 넣어주고 css로 숨기 처리 해주었다.

  .visuallyhidden {
    position: absolute;
    clip: rect(1px, 1px, 1px, 1px);
    -webkit-clip-path: inset(0px 0px 99.9% 99.9%);
    clip-path: inset(0px 0px 99.9% 99.9%);
    overflow: hidden;
    height: 1px;
    width: 1px;
    padding: 0;
    border: 0;
  }

material-icons 사용하기

프로젝트 시 대부분 리액트 아이콘을 사용해 왔었는데 이번에는 구글의 material-icons 사용해 보았다.

npm i @fontsource/material-icons 으로 설치 후 src/index.tsx 파일에서 패키지를 임포트 해준다. import '@fontsource/material-icons'

마지막으로 src/index.css에서 아이콘을 설정해 준다.

.material-icons {
  font-family: 'Material Icons';
  display: inline-block;
}

사용법은 무척 간단한데 아래처럼 span태크에 class를 material-icons으로 주고 value에 사용하고 싶은 아이콘 이름을 적어주면된다. 특정 상태를 기준으로 아이콘을 변경하고 싶다면 삼항연산자를 사용해 값을 작성해도 적용된다.

<span className="material-icons">{isNavActive ? 'close':'menu'}</span>

Nav 컴포넌트

react-scroll를 사용해 nav기능을 구현하였다. activeClass을 사용하면 해당 스크롤 위치에 있을 때만 사용할 class를 정해 줄 수 있다. 잘 작성했는데 적용이 잘 안된다면 새로고침을 해보자.

            <Link
              onClick={() => setIsNavActive(!isNavActive)}
              activeClass="underline decoration-solid"
              spy={true}
              to={to}
              smooth={true}
              duration={500}
            >

처음에는 Nav컴포넌트 하나로 구성했다가 반복되는 list부분을 분리했다. 모바일 버전와 데스크탑 버전에 중복해서 코드가 들어가니깐 컴포넌트화해서 빼는게 좋다고 생각했지만 사실 확신은 들지 않는다.. 🥴 이런 컴포넌트 구성은 반복되면 컴포넌트화 할려고 하지만 별로 사용 안 하는 거면 그냥 두는게 더 좋은가 싶기도해서 항상 고민이다. 그래도 빼니깐 보기는 깔끔해 보인다.

import React, { FC, useState } from 'react';
import NavList from './NavList';

export const Nav: FC = () => {
  const [isNavActive, setIsNavActive] = useState(false);

  return (
    <>
      {/* 데스크탑 버전 */}
      <nav className="hidden md:block md:fixed md:z-50 md:w-full md:p-5">
        <ul className="flex justify-end blackItalic gap-7">
          <NavList isNavActive={isNavActive} setIsNavActive={setIsNavActive} />
        </ul>
      </nav>

      {/* 모바일 버전 */}
      <nav className="fixed z-50 w-full p-5 md:hidden">
        <div
          className="relative z-40 flex items-center justify-center w-10 h-10 rounded-full highlighted"
          onClick={() => setIsNavActive(!isNavActive)}
        >
          <span className="text-2xl font-semibold material-icons">{isNavActive ? 'close':'menu'}</span>
        </div>
        {isNavActive && (
          <div className="absolute inset-0 w-full h-screen bg-white/90">
            <ul className="flex flex-col items-center justify-center h-full gap-10 blackItalic">
              <NavList isNavActive={isNavActive} setIsNavActive={setIsNavActive} />
            </ul>
          </div>
        )}
      </nav>
    </>
  );
};

export default Nav;

Title 컴포넌트와 useScrollObserver

Title이 스크롤에 맞춰서 밑줄은 옆으로 글자는 위로오는 애니메이션을 넣고 싶었다. 그래서 화면에 나오면 true로 사라지면 false로 알려주는 커스텀훅을 만들어 보기로 했다.

useRef로 여러개 요소를 사용할 수 있다고 해서 useRef 타입을 Element[]으로 하였다. 그리고 그 요소들의 화면 보임 여부를 저장할 배열인 상태를 추가 했다.

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

const useScrollObserver = (initalValue:boolean[]): [React.MutableRefObject<Element[]>, boolean[]] => {
  const targetRef = useRef<Element[]>([]);
  const [isVisible, setIsVisible] = useState<boolean[]>(initalValue);

  useEffect(() => {
    const observer = new IntersectionObserver ( entries => {
      const newIsVisible = isVisible
      entries.forEach( (entry, idx) => {
        newIsVisible[idx] =(entry.isIntersecting)
      })
      setIsVisible(newIsVisible)
    })

    if (targetRef.current) {
      targetRef.current.forEach( el => observer.observe(el));
    }

    return () => {
      if (targetRef.current) {
        
        targetRef.current.forEach( el => observer.unobserve(el));
      }
    };
  }, []);

  return [targetRef, isVisible];
};

export default useScrollObserver;

나는 ref가 [A, B, C, D]이라면 [ture, false, false, false]인 상태를 기대했는데..

배열 길이가 계속 다르고 첫번째 요소만 애니메이션이 적용이 되었다. 확인해 보니 observer가 요소마다 각각 적용되었으므로 요소 한 개의 값이 바뀌었다면 한 개의 isIntersecting만 확인해 idx로 구분했던게 소용이 없었던 것 이다.

그래서 idx가 아닌 키 값으로 구분해 주어야 할 것같은데 key로 어떤 걸 사용할지 엄청 고민하다가 target.id를 가져올 수 있길래 title을 id로 추가하고 그 값을 기준으로 상태를 변경하였다.

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

const useScrollObserver = (): [React.MutableRefObject<Element[]>, Record<string, boolean>] => {
  const targetRef = useRef<Element[]>([]);
  const [isVisible, setIsVisible] = useState<Record<string, boolean>>({});

  const observer = new IntersectionObserver((entries) => {
    const newIsVisible: Record<string, boolean> = {};

    entries.forEach((entry) => {
      const targetId = entry.target.id;
      if (targetId) {
        newIsVisible[targetId] = entry.isIntersecting;
      }
    });

    setIsVisible((prevIsVisible) => ({
      ...prevIsVisible,
      ...newIsVisible,
    }));
  });

  useEffect(() => {
    if (targetRef.current) {
      targetRef.current.forEach((el) => {
        observer.observe(el);
      });
    }

    return () => {
      if (targetRef.current) {
        targetRef.current.forEach((el) => {
          observer.unobserve(el);
        });
      }
    };
  }, [targetRef]);

  return [targetRef, isVisible];
};

export default useScrollObserver;

후기

tailwind 처음에는 사용 어색한데 쓰다보니 편하다. class 뭐로 해야하나 고민 안 해도 되서 너무 좋다. useScrollObserver 만드는데 생각보다 시간이 많이 걸렸다😵 역시 라이브러리가 짱인 것이다. 다음에는 framer-motion 써야지ㅋㅋ

참고
https://dalsong-00.tistory.com/35

profile
긍정적으로~✍️(◔◡◔)

0개의 댓글