본문 바로가기

공부/Next

Next 카카오맵을 활용한 시도별 가로 스크롤 버튼 이동 구현

반응형

 

사용자 경험을 고려해 카카오맵 기반 지역별 필터링과 버튼 스크롤 시 지도 이동 기능을 구현했습니다.

 

가로 스크롤 버튼 구현 📖

시, 도별 버튼이 화면 하단에 가로로 스크롤되며  버튼을 클릭하면 선택한 시, 도의 폴리곤이 지도에 렌더링 됩니다. `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;
}

 

 

아직 많이 부족합니다. 조언은 언제나 환영입니다~!!

 

반응형