-
Notifications
You must be signed in to change notification settings - Fork 1
네비게이션 바 애니메이션
dannysir edited this page Nov 22, 2024
·
1 revision
아래 보이는 기존 코드를 보면 로직이 다음과 같이 진행된다.
- searchParams를 이용해 버튼 클릭시 param을 변경한다.
- 만약 버튼이 현재 선택된 param과 같은 값이면 boarder를 추가한다.
기존 코드
import { useSearchParams } from "react-router-dom";
type MarketType = "전체" | "코스피" | "코스닥" | "나스닥";
export default function Nav() {
const [searchParams, setSearchParams] = useSearchParams();
const currentMarket = searchParams.get("top") || "전체";
const markets: MarketType[] = ["전체", "코스피", "코스닥", "나스닥"];
const handleMarketChange = (market: MarketType) => {
if (market === "전체") {
searchParams.delete("top");
setSearchParams(searchParams);
} else {
setSearchParams({ top: market });
}
};
return (
<div className="flex text-xl font-bold gap-1 px-3">
{markets.map((market) => (
<button
key={market}
onClick={() => handleMarketChange(market)}
className={`py-2 px-2 ${
currentMarket === market
? "border-b-4 border-juga-grayscale-black"
: ""
}`}
>
{market}
</button>
))}
</div>
);
}
기존 코드를 이용하면 각각의 버튼에 속해있는 boarder 이기 때문에 다른 영역으로 이동하는 애니메이션을 구현하기 어렵다. 따라서 아래 표시선이 이동하는 슬라이딩 애니메이션을 구현하기 위해서는 표시선(막대선)을 위한 별도의 엘리먼트가 필요하다.
따라서 아래와 같이 표시선을 위한 엘리먼트를 생성하고 전체 버튼 영역을 독립적으로 움직일 수 있게 만들었다. 또한 useRef
를 이용해 조작을 하기위해 ref를 지정했다.
const indicatorRef = useRef<HTMLDivElement>(null);
//... 생략
<div
ref={indicatorRef}
className='absolute bottom-0 h-1 bg-juga-grayscale-black transition-all duration-300'
style={{ height: '4px' }}
/>
useEffect를 이용한 애니메이션
앞선 표시선 엘리먼트를 선언하며 스타일로 transition을 주었기 때문에 서서히 변경되는 부분이 적용되었다.
이제 버튼을 클릭했을 때 바뀌는 currentMarket을 dependency로 이용해 버튼을 클릭할 때마다 표시선의 스타일을 변경하는 로직을 구현했다. 로직은 다음과 같은 순서로 진행된다.
-
buttonRefs
를 이용해 각각의 버튼을 배열로 가지고 있게 만든다. - useEffect 실행.
-
currentMarket
에 해당하는buttonRefs
를 가져온다. -
indicatorRef
로 표시선을 가져온다. -
indicatorRef
의 스타일을buttonRefs
에서 가져온 값을 바탕으로 수정한다.- left값 변경.
- 넓이 변경.
-
// currentMarket 선언 참고
const currentMarket = searchParams.get("top") || "전체";
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
useEffect(() => {
const currentButton = buttonRefs.current[markets.indexOf(currentMarket as MarketType)];
// 표시선 엘리먼트
const indicator = indicatorRef.current;
if (currentButton && indicator) {
indicator.style.left = `${currentButton.offsetLeft}px`;
indicator.style.width = `${currentButton.offsetWidth}px`;
}
}, [currentMarket]);
전체 코드
import { useSearchParams } from 'react-router-dom';
import { useEffect, useRef } from 'react';
type MarketType = '전체' | '코스피' | '코스닥' | '나스닥';
export default function Nav() {
const [searchParams, setSearchParams] = useSearchParams();
const currentMarket = searchParams.get('top') || '전체';
const indicatorRef = useRef<HTMLDivElement>(null);
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
const markets: MarketType[] = ['전체', '코스피', '코스닥', '나스닥'];
const handleMarketChange = (market: MarketType) => {
if (market === '전체') {
searchParams.delete('top');
setSearchParams(searchParams);
} else {
setSearchParams({ top: market });
}
};
useEffect(() => {
const currentButton =
buttonRefs.current[markets.indexOf(currentMarket as MarketType)];
const indicator = indicatorRef.current;
if (currentButton && indicator) {
indicator.style.left = `${currentButton.offsetLeft}px`;
indicator.style.width = `${currentButton.offsetWidth}px`;
}
}, [currentMarket]);
return (
<div className='relative flex gap-1 px-3 text-xl font-bold'>
<div
ref={indicatorRef}
className='absolute bottom-0 h-1 bg-juga-grayscale-black transition-all duration-300'
style={{ height: '4px' }}
/>
{markets.map((market, index) => (
<button
key={market}
ref={(el) => (buttonRefs.current[index] = el)}
onClick={() => handleMarketChange(market)}
className={`relative px-2 py-2`}
>
{market}
</button>
))}
</div>
);
}
- [FE] 프론트엔드 기술스택
- [FE] 라이브러리 없이 차트 구현 이유
- [FE] Canvas API 사용방법
- [FE] 네비게이션 바 애니메이션 구현
- [FE] Socket.io 사용방법
- [FE] Tanstack Router에 대하여...
- [FE] Intl(Internationalization) API
- [FE] React Suspense 적용
- [FE] 한글 입력 방식의 유연성을 높인 검색 시스템 구현하기
- [BE] 백엔드 기술 스택
- [BE] SSE vs Socket.io
- [BE] Redis를 도입하게 된 계기
- [BE] ACG Rule을 활용한 Secure CI CD 파이프라인 구현
- [BE] Nginx 로드밸런싱을 통해 한국 투자 API 소켓 제한 극복
- [BE] 주가 지수 기능 개발 과정
- [BE] 매수 및 매도 기능 개발 과정
- [BE] 실시간 자산 조회 기능 개발 과정
- [BE] 단위 테스트
- [BE] redis를 이용한 한국투자 Open API 세션 관리
- [BE] 데이터베이스 인덱싱
- [FE] React에서의 DOM 요소 접근 (useRef vs getElementById)
- [FE] Outlet을 활용한 공통 레이아웃 관리
- [FE] react hooks가 특정 조건에서 실행되면 안되는 이유 & useQuery에 query function 매개변수가 undefined일 수도 있을 때 어떻게 해결할까
- [FE] cross‐domain 로컬 환경에서 cookie로 인증 처리하기 with vite proxy
- [FE] 크롬&사파리 Composition 차이
- [FE] useEffect 의존성 배열
- [BE] Naver Cloud Platform HTTPS 무응답 현상
- [BE] 한국투자 Open API에서 access token을 발급받지 못하는 문제
- [BE] 한국투자 Open API와 웹소켓 연결이 되지 않던 문제
- [BE] 한국투자 Open API 웹소켓 연결이 중단되는 문제
- [BE] 같은 주식 주문이 동시에 여러 번 체결되는 문제
- [BE] 한국투자 Open API Websocket 세션을 두 개에서 한 개로 변경하기
- [BE] Nginx 로드 밸런싱 중 Socket bad Request 발생하는 현상
- [BE] 매수/매도 체결 로직에 의해 redis pub/sub이 정상적으로 동작하지 않는 문제