안녕하세요.
Next.js 14, typescript를 기반으로 한 프로젝트에서 카카오맵 api를 사용하고 있습니다.
그런데 카카오맵 최초 렌더링 후 맵 이동 및 줌 인/아웃 시 맵의 잔상이 남는 현상이 있습니다.
좀 더 설명하자면 처음 렌더링 직후 맵이 남아있는 모습입니다… ㅠ
그 상태에서 새로고침을 하면 정상적으로 잘 동작하며, 다른 페이지에서 다시 카카오맵이 있는 페이지로 이동하면
동일한 현상이 발생합니다.
말로 설명하기가 어려워서 영상파일과 코드 첨부합니다ㅠㅠ
"use client";
import { useEffect, useRef, useState } from "react";
import { toast } from "react-toastify";
import { pretendard } from "@/app/ui/fonts";
import { socketIO } from "@/lib/socketIO/realTime-socket";
import { IKakaoMapProps, IRover } from "@/types/device";
import ReservoirModal from "./reservoirModal";
import RoverList from "./roverList";
declare global {
interface Window {
kakao: any;
}
}
export default function KakaoMap({ geometry, roverList }: IKakaoMapProps) {
const kakaoMapRef = useRef<HTMLDivElement | null>(null);
const mapRef = useRef<any>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedRover, setSelectedRover] = useState<IRover | null>(null);
useEffect(() => {
// page 접속 시 connected 여부에 따라 소켓 연결
socketIO();
// `geometry` 에서 `longitude`와 `latitude`가 null이 아닌 것만 필터링
const validGeometry = geometry.filter(
(geo) => geo.longitude !== null && geo.latitude !== null,
);
// Kakao Maps API 로드 완료 여부 확인 및 초기화
const initializeMap = () => {
if (window.kakao && window.kakao.maps) {
window.kakao.maps.load(() => {
const options = {
center: new window.kakao.maps.LatLng(37.08021542, 127.27425854),
level: 7,
maxLevel: 11,
minLevel: 1,
mapTypeId: window.kakao.maps.MapTypeId.HYBRID,
};
// KakaoMap 생성 및 저장
const map = new window.kakao.maps.Map(kakaoMapRef.current, options);
mapRef.current = map;
// 클러스터러 설정
const clusterer = new window.kakao.maps.MarkerClusterer({
map: map,
averageCenter: true,
minLevel: 9,
});
// kakao map 범위 설정
const bounds = new window.kakao.maps.LatLngBounds();
// 각 rover 별 name, 위/경도, info 객체 생성
const markerPositions = roverList
.map((rover: IRover, index) => {
if (index < validGeometry.length) {
return {
name: rover.dev_nam,
latlng: new window.kakao.maps.LatLng(
validGeometry[index].latitude!,
validGeometry[index].longitude!,
),
roverInfo: rover, // Rover 정보 저장
};
}
return null;
})
.filter((position) => position !== null);
// @ts-expect-error
const markers: window.kakao.maps.Marker[] = [];
// marker별 이미지 설정
markerPositions.forEach((position) => {
// react-toastify 활용
// if (position.roverInfo.gpsQuality !== "4") {
// toast.error(`${position.roverInfo.dev_nam} RTK Error`);
// }
const imageSrc = "/rover.webp";
const imageSize = new window.kakao.maps.Size(65, 65);
const imageOption = { offset: new window.kakao.maps.Point(0, 0) };
const markerImage = new window.kakao.maps.MarkerImage(
imageSrc,
imageSize,
imageOption,
);
// marker 생성
const marker = new window.kakao.maps.Marker({
position: position.latlng,
name: position.name,
image: markerImage,
clickable: true,
});
markers.push(marker);
// marker bound 설정
bounds.extend(position.latlng);
// 마커 클릭 이벤트 리스너
window.kakao.maps.event.addListener(marker, "click", () => {
setSelectedRover(position.roverInfo);
setIsModalOpen(true);
});
});
// 클러스터러에 마커 추가
clusterer.addMarkers(markers);
// 마커 위치에 맞춰 지도 범위 설정
map.setBounds(bounds, 100, 50);
// 맵 타입 선택 기능 추가
map.addControl(
new window.kakao.maps.MapTypeControl(),
window.kakao.maps.ControlPosition.TOPRIGHT,
);
// 맵 줌 컨트롤 기능 추가
map.addControl(
new window.kakao.maps.ZoomControl(),
window.kakao.maps.ControlPosition.RIGHT,
);
});
} else {
console.error("Kakao Maps API가 로드되지 않았습니다.");
}
};
initializeMap();
return () => {};
}, [geometry, roverList]);
// 지도 중심 이동 함수
const panTo = (latitude: number | null, longitude: number | null) => {
if (latitude !== null && longitude !== null && mapRef.current) {
const moveLatLon = new window.kakao.maps.LatLng(latitude, longitude);
mapRef.current.panTo(moveLatLon);
}
};
const closeModal = () => {
setIsModalOpen(false);
setSelectedRover(null);
};
return (
<main>
<h1
className={`${pretendard.className} mb-4 text-xl md:text-2xl font-bold`}
>
저수지 수위관리 시스템
</h1>
<div
ref={kakaoMapRef}
className="h-[84vh] min-w-full z-0 rounded-2xl border-2 border-slate-200 shadow-xl"
></div>
<RoverList roverList={roverList} geometry={geometry} panTo={panTo} />
{isModalOpen && selectedRover && (
<ReservoirModal closeModal={closeModal} selectedRover={selectedRover} />
)}
</main>
);
}