메뉴 스크롤 동작 개선 및 코드 리팩토링

- 메뉴 버튼과 스크롤 위치 동기화 문제 해결
- 스크롤 업/다운 시 다른 오프셋 적용으로 정확한 카테고리 선택
- 버튼 클릭 시 해당 카테고리로 정확한 스크롤 이동
- 콘솔 로그 제거 및 코드 정리
- 상수 추출 및 타입 정의 개선
- 불필요한 import 및 변수 제거

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jay Sheen
2025-10-17 15:13:56 +09:00
parent 92150880dd
commit d74275acbd
2 changed files with 145 additions and 126 deletions

View File

@@ -139,7 +139,11 @@ export const MenuCategory = ({
<> <>
<div <div
className={`menu-category ${menuId}`} className={`menu-category ${menuId}`}
ref={ (element: HTMLDivElement) => { buttonRefs.current[itemIndex] = element } } ref={ (element: HTMLDivElement) => {
if (element) {
buttonRefs.current[itemIndex] = element;
}
} }
> >
<div className="category-header"> <div className="category-header">
<div className={ 'category-icon ' + menuIcon }></div> <div className={ 'category-icon ' + menuIcon }></div>

View File

@@ -9,13 +9,25 @@ import { FilterMotionDuration, FilterMotionStyle, FilterMotionVariants, MenuItem
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useLocation } from 'react-router'; import { useLocation } from 'react-router';
import { setHomeReloadKey } from '@/pages/home/home-page'; import { setHomeReloadKey } from '@/pages/home/home-page';
import { P } from 'node_modules/framer-motion/dist/types.d-Cjd591yU';
// 상수 정의
const SCROLL_ANIMATION_DURATION = 800;
const SCROLL_UP_OFFSET_PX = 150;
const BUTTON_SCROLL_OFFSET = 30;
// 타입 정의
interface ShortButton {
menuId: string;
menuName: string;
index: number;
}
export interface MenuProps { export interface MenuProps {
menuOn: boolean; menuOn: boolean;
setMenuOn: (menuOn: boolean) => void; setMenuOn: (menuOn: boolean) => void;
favoriteEdit?: boolean; favoriteEdit?: boolean;
}; }
export const Menu = ({ export const Menu = ({
menuOn, menuOn,
setMenuOn, setMenuOn,
@@ -24,40 +36,68 @@ export const Menu = ({
const userMids = useStore.getState().UserStore.userMids; const userMids = useStore.getState().UserStore.userMids;
const location = useLocation(); const location = useLocation();
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userInfo = useStore((state) => state.UserStore.userInfo);
const [shortBtns, setShortBtns] = useState<Array<Record<string, any>>>([]); const [shortBtns, setShortBtns] = useState<ShortButton[]>([]);
const [editMode, setEditMode] = useState<boolean>(false); const [editMode, setEditMode] = useState(false);
const [changeMenuId, setChangeMenuId] = useState<string | undefined>(); const [changeMenuId, setChangeMenuId] = useState<string | undefined>();
const [shortBtnIdx, setShortBtnIdx] = useState<number>(0); const [shortBtnIdx, setShortBtnIdx] = useState(0);
const [shortBoxOnScroll, setShortBoxOnScroll] = useState<boolean>(false);
const [menuListOnScroll, setMenuListOnScroll] = useState<boolean>(false);
const buttonRefs = useRef<Array<HTMLDivElement>>([]); const buttonRefs = useRef<Array<HTMLDivElement>>([]);
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const shortBtnScrollRef = useRef<HTMLDivElement>(null); const shortBtnScrollRef = useRef<HTMLDivElement>(null);
const isButtonScrolling = useRef<boolean>(false);
const scrollTimer = useRef<NodeJS.Timeout | null>(null);
const lastScrollTop = useRef<number>(0);
const onClickToNavigate = (path: string) => { const onClickToNavigate = (path: string) => {
onClickToMenuClose(); onClickToMenuClose();
navigate(path); navigate(path);
}; };
const scrollCategoryButtonToLeft = (index: number) => {
const buttonElement = shortBtnScrollRef.current?.children[index] as HTMLElement;
if (buttonElement && shortBtnScrollRef.current) {
const scrollLeft = buttonElement.offsetLeft - BUTTON_SCROLL_OFFSET;
shortBtnScrollRef.current.scrollTo({
top: 0,
left: scrollLeft,
behavior: 'smooth'
});
}
};
const onClickToMenuNavigate = (menuId: string, index: number) => { const onClickToMenuNavigate = (menuId: string, index: number) => {
setShortBoxOnScroll(false); isButtonScrolling.current = true;
setMenuListOnScroll(false);
let tops = [0, 255, 410, 565, 720, 875, 1030, 1500];
let lefts = [0, 85, 170, 275, 360, 450, 450, 450];
setShortBtnIdx(index); setShortBtnIdx(index);
scrollRef.current?.scrollTo({ scrollCategoryButtonToLeft(index);
top: tops[index],
const categoryElement = buttonRefs.current[index];
if (categoryElement && scrollRef.current) {
const scrollContainer = scrollRef.current;
const containerStyle = window.getComputedStyle(scrollContainer);
const paddingTop = parseFloat(containerStyle.paddingTop) || 0;
const scrollPosition = categoryElement.offsetTop - paddingTop;
lastScrollTop.current = scrollPosition;
scrollContainer.scrollTo({
top: scrollPosition,
left: 0, left: 0,
behavior: 'smooth' behavior: 'smooth'
}); });
shortBtnScrollRef.current?.scrollTo({ }
top: 0,
left: lefts[index],
behavior: 'smooth'
});
if (scrollTimer.current) {
clearTimeout(scrollTimer.current);
}
scrollTimer.current = setTimeout(() => {
if (scrollRef.current) {
const actualScrollTop = scrollRef.current.scrollTop;
lastScrollTop.current = actualScrollTop;
setShortBtnIdx(index);
}
isButtonScrolling.current = false;
}, SCROLL_ANIMATION_DURATION);
}; };
const onClickToMenuClose = () => { const onClickToMenuClose = () => {
if(editMode){ if(editMode){
@@ -93,103 +133,83 @@ export const Menu = ({
}; };
const shortBtnsSetting = () => { const shortBtnsSetting = () => {
let shortList = []; const shortList: ShortButton[] = MenuItems.map((item, index) => ({
for(let i=0;i<MenuItems.length;i++) { menuId: item.menuId,
shortList.push({ menuName: item.menuName,
menuId: MenuItems[i]?.menuId, index
menuName: MenuItems[i]?.menuName, }));
index: i
});
}
setShortBtns(shortList); setShortBtns(shortList);
}; };
const shortBtnScroll = (e: any) => { const getCurrentCategoryIndex = (scrollTop: number): number => {
if(shortBoxOnScroll){ if (buttonRefs.current.length === 0 || !scrollRef.current) return 0;
let x = shortBtnScrollRef.current?.scrollLeft;
let top = 0; const containerStyle = window.getComputedStyle(scrollRef.current);
if(x){ const paddingTop = parseFloat(containerStyle.paddingTop) || 0;
if(x < 85){ const adjustedScrollTop = scrollTop + paddingTop;
top = 0; const isScrollingUp = scrollTop < lastScrollTop.current;
setShortBtnIdx(0);
if (isScrollingUp) {
// 스크롤 업: 화면 상단 기준으로 카테고리 선택
const checkPoint = scrollTop + paddingTop + SCROLL_UP_OFFSET_PX;
for (let i = buttonRefs.current.length - 1; i >= 0; i--) {
const element = buttonRefs.current[i];
if (!element) continue;
const currentTop = element.offsetTop;
if (checkPoint >= currentTop) {
if (i < buttonRefs.current.length - 1) {
const nextElement = buttonRefs.current[i + 1];
if (nextElement && checkPoint >= nextElement.offsetTop) {
continue;
} }
else if(x < 170){
top = 255;
setShortBtnIdx(1);
} }
else if(x < 275){ return i;
top = 410;
setShortBtnIdx(2);
} }
else if(x < 360){
top = 565;
setShortBtnIdx(3);
} }
else if(x < 450){ } else {
top = 720; // 스크롤 다운: 카테고리 시작점 기준
setShortBtnIdx(4); for (let i = buttonRefs.current.length - 1; i >= 0; i--) {
const element = buttonRefs.current[i];
if (!element) continue;
const currentTop = element.offsetTop;
if (adjustedScrollTop >= currentTop) {
if (i === buttonRefs.current.length - 1) {
return i;
}
const nextElement = buttonRefs.current[i + 1];
if (nextElement) {
const nextTop = nextElement.offsetTop;
if (adjustedScrollTop < nextTop) {
return i;
}
} else {
return i;
}
} }
else{
top = 875;
setShortBtnIdx(5);
} }
} }
scrollRef.current?.scrollTo({ return 0;
top: top,
left: 0,
behavior: 'smooth'
});
}
}; };
const menuListScroll = () => { const menuListScroll = () => {
if(menuListOnScroll){ if (isButtonScrolling.current) return;
let y = scrollRef.current?.scrollTop;
let left = 0;
if(y){
if(y < 255){
left = 0;
}
else if(y < 410){
left = 85;
}
else if(y < 565){
left = 170;
}
else if(y < 720){
left = 275;
}
else if(y < 875){
left = 360;
}
else if(y < 1030){
left = 450;
}
else{
left = 450;
}
}
shortBtnScrollRef.current?.scrollTo({
top: 0,
left: left,
behavior: 'smooth'
});
}
};
const shortBoxTouchStart = () => {
setShortBoxOnScroll(true);
setMenuListOnScroll(false);
};
const shortBoxTouchEnd = () => {
}; const scrollTop = scrollRef.current?.scrollTop || 0;
const menuListTouchStart = () => { const currentIndex = getCurrentCategoryIndex(scrollTop);
setShortBoxOnScroll(false);
setMenuListOnScroll(true);
};
const menuListTouchEnd = () => {
if (currentIndex !== shortBtnIdx) {
setShortBtnIdx(currentIndex);
scrollCategoryButtonToLeft(currentIndex);
}
lastScrollTop.current = scrollTop;
}; };
useEffect(() => { useEffect(() => {
@@ -258,16 +278,13 @@ export const Menu = ({
<div <div
className="full-menu-keywords" className="full-menu-keywords"
ref={ shortBtnScrollRef } ref={ shortBtnScrollRef }
onScroll={ shortBtnScroll }
onTouchStart={ shortBoxTouchStart }
onTouchEnd={ shortBoxTouchEnd }
> >
{ {
shortBtns.map((value, index) => ( shortBtns.map((value, index) => (
<span <span
key={ `short-btn-${value.menuName}` } key={ `short-btn-${value.menuName}` }
className={ `keyword-tag ${(shortBtnIdx === index)? 'active': ''}` } className={ `keyword-tag ${(shortBtnIdx === index)? 'active': ''}` }
onClick={ () => onClickToMenuNavigate(value.menuId, value.index) } onClick={ () => onClickToMenuNavigate(value.menuId, index) }
>{ value.menuName }</span> >{ value.menuName }</span>
)) ))
} }
@@ -278,8 +295,6 @@ export const Menu = ({
className="full-menu-list" className="full-menu-list"
ref={ scrollRef } ref={ scrollRef }
onScroll={ menuListScroll } onScroll={ menuListScroll }
onTouchStart={ menuListTouchStart }
onTouchEnd={ menuListTouchEnd }
> >
{ getMenuCategory() } { getMenuCategory() }
</div> </div>