구현하려고 한 기능은 사용자가 주소 입력 시 위/경도를 계산해서 지도에 출력하는 기능이었다.
구글링을 해봐도 카카오/구글 developer로 이동해서 Html/Js/Css로 나오고 React 관련되어서 나오길래
여러 블로그를 테스트 해보다가 Next.js에 적용했던 과정을 기록했다.
Next.js..... kakao 지도 관련 글이 어떻게 이렇게 없지😢
1. 카카오 지도 Api Key 발급 받기
주소를 위/경도로 변환해주는 기능을 Geocoding, Geocoder라고 한다. 구글링할 때 카카오 geocoder라고 치면 나온다.
2. 주소 입력 👉 위/경도 출력하기
https://apis.map.kakao.com/web/sample/addr2coord/
3. 카카오 지도 생성 (+ 확대/축소, 원래 위치로 이동하기)
+ 구현 중 오류 발생 시 참고
- Cannot read property 'Geocoder' of undefined error 발생 시
- Api Key 환경변수 설정하는 방법
- 브라우저 참고용 : NEXT_PUBLIC_
4. 구현 화면
+ 구현 코드 (JS/CSS)
더보기
import { useEffect, useState } from "react";
import { CustomOverlayMap, Map, MapMarker } from "react-kakao-maps-sdk";
import AddIcon from "@mui/icons-material/Add";
import MinusIcon from "@mui/icons-material/Remove";
import RefreshIcon from "@mui/icons-material/Refresh";
const Address = () => {
const [state, setState] = useState({
// 지도의 초기 위치
center: { lat: 33.452613, lng: 126.570888 },
// 부드럽게 이동할 지 유무
isPanto: true,
// 검색한 주소
address: "경기도 부천시 중동 1143-3 KR",
// 지도 레벨
level: 3,
});
const [address, setAddress] = useState("경기도 부천시 중동 1143-3 KR"); // 임시
const [submit, setSubmit] = useState(false); // 임시
const [open, setOpen] = useState(true); // overlay 오픈 유무
const mapBtnObj = {
plus: () => {
setState((prev) => {
return {
...prev,
level: prev.level + 1,
};
});
},
minus: () => {
setState((prev) => {
return {
...prev,
level: prev.level - 1,
};
});
},
reset: () => {
// 지도 중심 좌표로 이동 시킬 때 타입과 숫자가 같으면 동작을 안해서 누를 때마다 타입 확인 후 변경 해줘서 기능 구현
const latType = typeof state.center.lat;
const lngType = typeof state.center.lng;
setState((prev) => {
return {
...prev,
center: {
lat:
latType === "number"
? String(prev.center.lat)
: Number(prev.center.lat),
lng:
lngType === "number"
? String(prev.center.lng)
: Number(prev.center.lng),
},
level: 3,
};
});
},
};
useEffect(() => {
kakaoMapGeoCoder();
setSubmit(false);
}, [submit]);
const kakaoMapGeoCoder = () => {
window.kakao.maps.load(() => {
// 주소-좌표 변환 객체를 생성합니다
const geocoder = new window.kakao.maps.services.Geocoder();
// 주소로 좌표를 검색합니다
geocoder.addressSearch(address, function (result, status) {
// 정상적으로 검색이 완료됐으면
if (status === window.kakao.maps.services.Status.OK) {
setState((prev) => {
return {
...prev,
center: {
lat: Number(result[0].y),
lng: Number(result[0].x),
},
isPanto: !prev.isPanto,
};
});
}
});
});
};
return (
<>
<div className="mapWrap">
<Map
className="map"
center={state.center}
isPanto={state.isPanto}
level={state.level} // 지도의 확대 레벨
>
<MapMarker position={state.center}></MapMarker>
<CustomOverlayMap position={state.center} yAnchor={1}>
<div className="customoverlay">
<a
href={`https://map.kakao.com/link/map/${state.address},${state.center.lat},${state.center.lng}`}
target="_blank"
rel="noreferrer"
>
<span className="title">{state.address}</span>
</a>
</div>
</CustomOverlayMap>
</Map>
<div className="mapBtnWrap">
<button
className="mapBtn"
onClick={() => {
mapBtnObj["minus"]();
}}
>
<AddIcon />
</button>
<button
className="mapBtn"
onClick={() => {
mapBtnObj["plus"]();
}}
>
<MinusIcon />
</button>
<br />
<button
className="mapBtn"
onClick={() => {
mapBtnObj["reset"]();
}}
>
<RefreshIcon />
</button>
</div>
{open && (
<div
className="pointer"
onClick={() => {
setOpen((prev) => {
return !prev;
});
}}
>
<span className="openMapText">지도를 보려면 클릭하세요.</span>
<div className="mapOverlay"></div>
</div>
)}
</div>
{/* 아래 부분 임시 */}
<div style={{ marginTop: "600px" }}>
<p>주소를 입력 해주세요.</p>
<input
type="text"
value={address}
onChange={(e) => {
setAddress(e.target.value);
}}
/>
<p>위도: {state.center.lat}</p>
<p>경도: {state.center.lng}</p>
<button
onClick={() => {
setState((prev) => {
return { ...prev, address: address };
});
setSubmit(true);
}}
>
저장
</button>
</div>
</>
);
};
export default Address;
/* 폰트 변경 할 때 */
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.5/dist/web/static/pretendard.css");
body {
margin: 0;
padding: 0 15px;
}
body * {
font-family: "Pretendard";
word-break: keep-all;
white-space: pre-line;
transition: all 0.5s;
}
.pointer {
cursor: pointer;
}
.mapWrap {
position: static;
}
.map {
width: calc(100% - 30px);
height: 547px;
border-radius: 12px;
position: absolute;
top: 0;
left: 15px;
}
.map .customoverlay a {
position: absolute;
z-index: 3;
top: -73px;
left: 50%;
transform: translateX(-50%);
border: 1px solid #e5e5e5;
}
.map .customoverlay a:before {
position: absolute;
left: 0;
right: 0;
bottom: -17px;
width: 17px;
height: 18px;
margin: auto;
background: url("..//public/img/mapArrowBottom.png") no-repeat;
content: "";
}
.map .customoverlay span {
display: inline-block;
padding: 4px 8px 4px;
border-radius: 3px;
font-weight: 600;
font-size: 11px;
line-height: 15px;
background: white;
color: #000;
z-index: 10;
white-space: nowrap;
}
.mapBtnWrap {
position: absolute;
top: 200px;
right: 25px;
z-index: 11;
}
.mapBtn {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
background: white;
border-radius: 8px;
border: 1px solid #e5e5e5;
cursor: pointer;
margin: 2px 0px;
}
.mapBtn:hover {
background: #e5e5e5;
}
.openMapText {
position: absolute;
top: 20px;
left: 35px;
font-weight: 500;
color: #fff;
z-index: 13;
}
.mapOverlay {
width: calc(100% - 30px);
height: 387px;
position: absolute;
top: 0;
left: 15px;
background: #000;
opacity: 0.3;
color: #fff;
padding-top: 160px;
text-align: center;
z-index: 12;
border-radius: 12px;
}
'더 알아보기 > 기능' 카테고리의 다른 글
[react-datepicker] 달력 구현 및 주말/양력 휴일/음력 휴일 비활성화 하기 (0) | 2023.01.20 |
---|---|
반응형 footer 하단 고정하기 (0) | 2023.01.17 |
css 전역화 시키기 2️⃣ (ver. :root) (0) | 2023.01.06 |
css 전역화 시키기 1️⃣ (ver. ThemeProvider) (2) | 2023.01.06 |
크롬 비밀번호 자동완성 비활성화 (0) | 2022.12.13 |
댓글