반응형
사용자 경험을 고려해 카카오맵 기반 지역별 필터링과 버튼 스크롤 시 지도 이동 기능을 구현했습니다.
가로 스크롤 버튼 구현 📖
시, 도별 버튼이 화면 하단에 가로로 스크롤되며 버튼을 클릭하면 선택한 시, 도의 폴리곤이 지도에 렌더링 됩니다. `Swiper`라이브러리를 사용해 구현했습니다.
<Swiper
slidesPerView={5}
centeredSlides={true}
pagination={{ clickable: true }}
navigation={true}
>
<SwiperSlide>전체</SwiperSlide>
{siDoName.map((sido) => (
<SwiperSlide key={sido.key}>{sido.name}</SwiperSlide>
))}
</Swiper>
시, 도 선택에 따른 지도 이동 및 폴리곤 렌더링 📖
선택한 시, 도 폴리곤 필터링
사용자가 시, 도를 클릭하면 해당 시, 도의 좌표 데이터를 바탕으로 폴리곤 path를 업데이트 하고 지도를 그립니다.
const updatePolygonPath = (path: PathType, index: number) => {
setSelectedPath(path); // 선택된 시도의 path만 지도에 렌더링
};
버튼과 폴리곤 동기화
선택된 지역에 따라 `slideTo` 메서드를 사용해 버튼의 index 위치를 이동시킵니다.
useEffect(() => {
if (swiperRef.current) {
swiperRef.current.slideTo(activeIndex);
}
}, [activeIndex]);
전체 코드 📖
KakaoMpa.tsx
// KakaoMap.tsx
'use client';
import { Map, Polygon } from 'react-kakao-maps-sdk';
import useGeoData from '@/hooks/useGeoData';
import { MAP_COLOR } from '@/constants/mapColor';
import { useCallback, useState } from 'react';
import { PathType } from '@/types/kakaomap/CoordRegionCode.type';
import ReSetttingMapBounds from '@/components/stampMap/ReSetttingMapBounds';
import ScrollButtonSwiper from '@/components/stampMap/ScrollButtonSwiper';
import KakaoMapMarker from './KakaoMapMarker';
const KakaoMap = () => {
const [location, setLocation] = useState({
center: { lat: 35.90701, lng: 127.570667 },
isPanto: true
});
// 선택된 경로 상태 추가
const [selectedPath, setSelectedPath] = useState<PathType>([]);
// 선택된 슬라이드 index
const [activeIndex, setActiveIndex] = useState(0);
// 폴리곤 리스트
const { geoList, setGeoList } = useGeoData();
// 폴리곤 hover 업데이트
const updateHoverState = useCallback(
(key: number, isHover: boolean) => {
setGeoList((prevGeoList) => prevGeoList.map((area) => (area.key === key ? { ...area, isHover } : area)));
},
[setGeoList]
);
const updatePolygonPath = (path: PathType, index: number) => {
setActiveIndex(index + 1); // 클릭한 폴리곤 index 저장
setSelectedPath(path); // 클릭한 폴리곤의 path를 상태에 저장
};
return (
<>
<Map
id="map"
center={location.center}
isPanto={location.isPanto}
className="relative h-[100vh] w-[100vw]"
level={12}
>
{activeIndex === 0 ? (
geoList.map((item, index) => {
const { key, path, isHover } = item;
const color = MAP_COLOR[index];
return (
<Polygon
key={key}
path={path}
strokeWeight={2} // 선 두께
strokeColor={color} // 선 색깔
strokeOpacity={0.7} // 선 불투명도
strokeStyle={'solid'} // 선 스타일
fillColor={isHover ? color : '#ffffff'} // 채우기 색깔
fillOpacity={0.2} // 채우기 불투명도
onMouseover={() => updateHoverState(key, true)}
onMouseout={() => updateHoverState(key, false)}
onClick={() => updatePolygonPath(path, index)}
/>
);
})
) : (
<Polygon
key={activeIndex}
path={selectedPath}
strokeWeight={2}
strokeColor={MAP_COLOR[activeIndex - 1]}
strokeOpacity={0.7}
strokeStyle="solid"
fillColor={MAP_COLOR[activeIndex - 1]}
fillOpacity={0.1}
/>
)}
<ReSetttingMapBounds paths={selectedPath} />
</Map>
<ScrollButtonSwiper activeIndex={activeIndex} setActiveIndex={setActiveIndex} setSelectedPath={setSelectedPath} />
</>
);
};
export default KakaoMap;
ScrollButtonSwiper.tsx
// ScrollButtonSwiper.tsx
'use client';
import React, { useEffect, useRef } from 'react';
import { Navigation, Pagination } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Swiper as SwiperProps } from 'swiper/types';
import '@/styles/swiper.css';
import 'swiper/css';
import 'swiper/css/pagination';
import useGeoData from '@/hooks/useGeoData';
import { PathType } from '@/types/kakaomap/CoordRegionCode.type';
interface ScrollButtonSwiperPropsType {
activeIndex: number;
setActiveIndex: (activeIndex: number) => void;
setSelectedPath: (path: PathType) => void;
}
const ScrollButtonSwiper = ({ activeIndex, setActiveIndex, setSelectedPath }: ScrollButtonSwiperPropsType) => {
const { siDoName } = useGeoData();
const swiperRef = useRef<any>(null); // Swiper 인스턴스 참조
useEffect(() => {
if (swiperRef.current) {
swiperRef.current.slideTo(activeIndex); // activeIndex 변경 시 slide 이동
}
}, [activeIndex]);
const onSlideChangeHandler = (swiper: SwiperProps) => {
const activeIndex = swiper.activeIndex;
setActiveIndex(activeIndex);
if (activeIndex === 0) {
// 전체 선택 시 모든 path 병합 후 설정
const allPaths = siDoName.flatMap((sido) => sido.path);
setSelectedPath(allPaths);
} else {
// 특정 시/도 선택 시 해당 path 설정
setSelectedPath(siDoName[activeIndex - 1].path);
}
};
return (
<div className="fixed bottom-0 left-2/4 z-[99] w-[100vw] -translate-x-1/2 transform bg-gradient-to-t from-[rgba(66,66,66,0.8)] to-[rgba(255,255,255,0)]">
<Swiper
onSwiper={(swiper) => (swiperRef.current = swiper)}
slidesPerView={5}
// spaceBetween={30}
centeredSlides={true}
grabCursor={true}
pagination={{
clickable: true,
dynamicBullets: true
}}
navigation={true}
onSlideChange={onSlideChangeHandler}
modules={[Navigation, Pagination]}
className="swiper"
>
<SwiperSlide>
<div className={`swiper-slide ${activeIndex === 0 ? 'swiper-slide-active' : ''}`}>전체</div>
</SwiperSlide>
{siDoName.map((sido, index) => (
<SwiperSlide key={sido.key}>
<div className={`swiper-slide ${activeIndex === index + 1 ? 'swiper-slide-active' : ''}`}>{sido.name}</div>
</SwiperSlide>
))}
</Swiper>
</div>
);
};
export default ScrollButtonSwiper;
swiper.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.swiper {
@apply m-4 w-[600px] bg-transparent;
}
.swiper-slide {
@apply flex w-[120px] items-center justify-center p-2.5 text-center text-[#6f6f6f];
}
.swiper-slide-active {
@apply cursor-pointer font-bold text-black;
}
}
.swiper-pagination {
--swiper-pagination-bottom: 0;
}
.swiper-pagination-bullet {
--swiper-pagination-color: #1f2937;
}
아직 많이 부족합니다. 조언은 언제나 환영입니다~!!
반응형
'공부 > Next' 카테고리의 다른 글
[리팩토링] queries 폴더 구조 변경 및 메서드명 수정 (1) | 2024.11.07 |
---|---|
[리팩토링] Icon 컴포넌트 리팩토링 (0) | 2024.11.02 |
Next 커스텀 훅을 사용한 모달창 기능 구현 (1) | 2024.11.01 |
[리팩토링] 데이터 로직 분리 및 서버 상태 관리 최적화 (0) | 2024.10.28 |
[리팩토링] Next 카카오 맵 폴리곤 렌더링 리팩토링 - 커스텀 훅, 유틸 함수 구조 개선 (0) | 2024.10.24 |
Next 카카오 맵 행정구역 나누기 (1) | 2024.10.23 |
Next 카카오 맵 구현 (0) | 2024.10.23 |
[Next, Supabase Auth] 이메일, 소셜 로그인 기능 구현 및 트리거 설정 (2) | 2024.10.14 |