일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- userevent_tracker
- code editor
- FirebaseAnalytics
- node
- Prism.js
- swagger-typescript-api
- Game js
- webrtc
- Three-fiber
- Completer
- react
- typescript
- Redux
- web track
- uint16array
- RouteObserver
- Flutter
- babel
- KakaoMap
- Image Resize typescript
- Three js
- identifierForVender
- Excel
- methodChannel
- REST API
- Raycasting
- Babel standalone
- androidId
- jszip
- uint8array
- Today
- Total
Never give up
React - Kakaomap example 본문
오늘은 간단하게 react에서 kakao map을 만들어봤습니다
먼저 세팅부분은 가이드에 나와있는대로 해주고
(링크 : https://apis.map.kakao.com/web/guide/)
카카오맵 웹 예제에 나와있는 키워드 목록을 포함한 부분으로 테스트를 해봤습니다
(링크 : https://apis.map.kakao.com/web/sample/keywordList/)
Kakaomap example.js
/*global kakao*/
import React, { Component } from 'react'
import './kakaomap_example.css'
class KakaoMapExample extends Component {
constructor() {
super()
this.markers = []
this.map = null
this.ps = null
this.infowindow = null
this.keyword = ''
this.isCalled = false
}
componentDidMount() {
this.script = document.createElement('script')
this.script.async = true
this.script.src = `https://dapi.kakao.com/v2/maps/sdk.js?autoload=false&libraries=services&appkey=${process.env.REACT_APP_KAKAO_MAP_KEY}`
document.head.appendChild(this.script)
this.script.onload = () => {
kakao.maps.load(() => {
this.loadOption()
})
}
}
componentWillUnmount(){
document.head.removeChild(this.script)
}
loadOption = () => {
const mapContainer = document.getElementById('map')
const mapOption = {
center: new kakao.maps.LatLng(33.450701, 126.570667),
level: 3
};
this.map = new kakao.maps.Map(mapContainer, mapOption);
const mapTypeControl = new kakao.maps.MapTypeControl();
this.map.addControl(mapTypeControl, kakao.maps.ControlPosition.TOPRIGHT);
const zoomControl = new kakao.maps.ZoomControl();
this.map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT);
this.ps = new kakao.maps.services.Places();
this.infowindow = new kakao.maps.InfoWindow({ zIndex: 1 });
}
searchPlaces = () => {
if (!this.keyword.replace(/^\s+|\s+$/g, '')) {
alert('키워드를 입력해주세요!');
return false;
}
this.ps.keywordSearch(this.keyword, this.placesSearchCB);
}
placesSearchCB = (data, status, pagination) => {
if (this.isCalled) {
return
}
this.isCalled = true
setTimeout(() => this.isCalled = false, 3000)
if (status === kakao.maps.services.Status.OK) {
this.displayPlaces(data);
this.displayPagination(pagination);
} else if (status === kakao.maps.services.Status.ZERO_RESULT) {
alert('검색 결과가 존재하지 않습니다.');
return;
} else if (status === kakao.maps.services.Status.ERROR) {
alert('검색 결과 중 오류가 발생했습니다.');
return;
}
}
displayPlaces = (places) => {
const listEl = document.getElementById('placesList')
const menuEl = document.getElementById('menu_wrap')
const fragment = document.createDocumentFragment()
const bounds = new kakao.maps.LatLngBounds()
this.removeAllChildNods(listEl);
this.removeMarker();
console.log({ places }, places.length)
for (let i = 0; i < places.length; i++) {
const placePosition = new kakao.maps.LatLng(places[i].y, places[i].x)
const marker = this.addMarker(placePosition, i)
const itemEl = this.getListItem(i, places[i]);
bounds.extend(placePosition);
const title = places[i].place_name
kakao.maps.event.addListener(marker, 'mouseover', () => {
this.displayInfowindow(marker, title);
});
kakao.maps.event.addListener(marker, 'mouseout', () => {
this.infowindow.close();
});
itemEl.onmouseover = () => {
this.displayInfowindow(marker, title);
};
itemEl.onmouseout = () => {
this.infowindow.close();
};
fragment.appendChild(itemEl);
}
listEl.appendChild(fragment);
menuEl.scrollTop = 0;
this.map.setBounds(bounds);
}
getListItem = (index, places) => {
const el = document.createElement('li')
let itemStr = '<span class="markerbg marker_' + (index + 1) + '"></span>' +
'<div class="info">' +
' <h5>' + places.place_name + '</h5>';
if (places.road_address_name) {
itemStr += ' <span>' + places.road_address_name + '</span>' +
' <span class="jibun gray">' + places.address_name + '</span>';
} else {
itemStr += ' <span>' + places.address_name + '</span>';
}
itemStr += ' <span class="tel">' + places.phone + '</span>' +
'</div>';
el.innerHTML = itemStr;
el.className = 'item';
return el;
}
addMarker = (position, idx, title) => {
const imageSrc = 'https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/marker_number_blue.png'
const imageSize = new kakao.maps.Size(36, 37)
const imgOptions = {
spriteSize: new kakao.maps.Size(36, 691),
spriteOrigin: new kakao.maps.Point(0, (idx * 46) + 10),
offset: new kakao.maps.Point(13, 37)
}
const markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imgOptions)
const marker = new kakao.maps.Marker({
position: position,
image: markerImage
});
marker.setMap(this.map);
this.markers.push(marker);
return marker;
}
removeMarker = () => {
this.markers.forEach((e, i) => {
this.markers[i].setMap(null);
})
this.markers = [];
}
displayPagination = (pagination) => {
const paginationEl = document.getElementById('pagination')
const fragment = document.createDocumentFragment()
while (paginationEl.hasChildNodes()) {
paginationEl.removeChild(paginationEl.lastChild);
}
let elList = []
for (let i = 1; i <= pagination.last; i++) {
elList[i] = document.createElement('button')
elList[i].innerHTML = i;
if (i === pagination.current) {
elList[i].className = 'on'
}
elList[i].onclick = async () => {
elList.forEach((_,i) => {
elList[i].className = 'off'
})
elList[i].className = 'on'
const res = await fetch(`https://dapi.kakao.com/v2/local/search/keyword.json?page=${i}&size=15&sort=accuracy&query=${this.keyword}`, {
headers: {
'Authorization': `KakaoAK ${process.env.REACT_APP_KAKAO_RESTAPI_KEY}`
}
})
const json = await res.json()
this.displayPlaces(json['documents'])
}
fragment.appendChild(elList[i]);
}
paginationEl.appendChild(fragment);
}
displayInfowindow = (marker, title) => {
const content = `<div style="padding:5px;z-index:1;">${title}</div>`
this.infowindow.setContent(content);
this.infowindow.open(this.map, marker);
}
removeAllChildNods = (el) => {
while (el.hasChildNodes()) {
el.removeChild(el.lastChild);
}
}
render() {
return (
<>
<div id='map' style={{ width: '100vw', height: '100vh' }} />
<div id="menu_wrap" className="bg_white">
<div className="option">
<div>
키워드 : <input type="text" onChange={(e) => this.keyword = e.target.value} size="15" />
<button onClick={() => this.searchPlaces()}>검색하기</button>
</div>
</div>
<hr />
<ul id="placesList"></ul>
<div id="pagination"></div>
</div>
</>
)
}
}
export default KakaoMapExample
kakaomap_example.css
#map {
width: 100vw;
height: 100vh;
}
#menu_wrap {
position: absolute;
top: 0;
left: 0;
width: 250px;
height: 700px;
margin: 10px 0 30px 10px;
padding: 5px;
overflow-y: auto;
background: rgba(255, 255, 255, 0.7);
z-index: 2;
font-size: 12px;
border-radius: 10px;
}
.bg_white {
background: #fff;
}
#menu_wrap hr {
display: block;
height: 1px;
border: 0;
border-top: 2px solid #5F5F5F;
margin: 3px 0;
}
#menu_wrap .option {
text-align: center;
}
#menu_wrap .option p {
margin: 10px 0;
}
#menu_wrap .option button {
margin-left: 5px;
}
#placesList li {
list-style: none;
}
#placesList .item {
position: relative;
border-bottom: 1px solid #888;
overflow: hidden;
cursor: pointer;
min-height: 65px;
}
#placesList .item span {
display: block;
margin-top: 4px;
}
#placesList .item h5,
#placesList .item .info {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
#placesList .item .info {
padding: 10px 0 10px 55px;
}
#placesList .info .gray {
color: #8a8a8a;
}
#placesList .info .jibun {
padding-left: 26px;
background: url(https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/places_jibun.png) no-repeat;
}
#placesList .info .tel {
color: #009900;
}
#placesList .item .markerbg {
float: left;
position: absolute;
width: 36px;
height: 37px;
margin: 10px 0 0 10px;
background: url(https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/marker_number_blue.png) no-repeat;
}
#placesList .item .marker_1 {
background-position: 0 -10px;
}
#placesList .item .marker_2 {
background-position: 0 -56px;
}
#placesList .item .marker_3 {
background-position: 0 -102px
}
#placesList .item .marker_4 {
background-position: 0 -148px;
}
#placesList .item .marker_5 {
background-position: 0 -194px;
}
#placesList .item .marker_6 {
background-position: 0 -240px;
}
#placesList .item .marker_7 {
background-position: 0 -286px;
}
#placesList .item .marker_8 {
background-position: 0 -332px;
}
#placesList .item .marker_9 {
background-position: 0 -378px;
}
#placesList .item .marker_10 {
background-position: 0 -423px;
}
#placesList .item .marker_11 {
background-position: 0 -470px;
}
#placesList .item .marker_12 {
background-position: 0 -516px;
}
#placesList .item .marker_13 {
background-position: 0 -562px;
}
#placesList .item .marker_14 {
background-position: 0 -608px;
}
#placesList .item .marker_15 {
background-position: 0 -654px;
}
#pagination {
margin: 10px auto;
text-align: center;
}
#pagination button {
display: inline-block;
background-color: transparent;
border: none;
margin-right: 10px;
cursor: pointer;
}
#pagination button:hover{
color: #777;
}
#pagination .on {
font-weight: bold;
cursor: pointer;
background-color: transparent;
color: #777;
}
뭔가 굉장히 코드량이 많아보이지만 예제에 나와있는거 적당히(?) 짜집기 +@로 만들었습니다
먼저 바인딩하는 부분에서는 고민이 조금 필요했는데 여기저기 참고해서 필자가 적용한 부분은
componentDidMount부분에서 header에 script를 추가해줍니다
(autoload=false 필수)
script가 로드가 완료되면 카카오맵 예제에 나와있는 코드들을 적당히(?) react에 맞춰서 코딩을 해줍니다
추가로 vanila js에서는 spa와 달리 pagination부분에서 링크를 변경해주지만
필자는 rest api key를 이용해서 페이지에 따른 api콜을 다시 해주고 있습니다
displayPagination메소드 내부에서 pagination으로 페이지들을 생성할 때,
각각 버튼에 api콜을 다시해줍니다
const res = await fetch(`https://dapi.kakao.com/v2/local/search/keyword.json?page=${i}&size=15&sort=accuracy&query=${this.keyword}`, {
headers: {
'Authorization': `KakaoAK ${process.env.REACT_APP_KAKAO_RESTAPI_KEY}`
}
})
page에 index값을 전달하고 keyword query부분에 keyword값을 넣어주고
header에 authorization에 api key를 셋팅해주고 콜을합니다
이후에 결과값을 displayPlaces 메소드에 넣어주면 마커들이 갱신됩니다
사실 kakao map webview 패키지도 동일한 방식으로 하면 될텐데 귀찮아서...
'WEB' 카테고리의 다른 글
Node - Google analytics (feat. Chart.js, Excel) (0) | 2022.04.22 |
---|---|
React, Node - Image upload example (0) | 2022.02.19 |
React - html, css album page clone (0) | 2022.01.08 |
React, Node - Rest API with sqlite3 (beginner) - 2 (0) | 2022.01.08 |
React, Node - Rest API with sqlite3 (beginner) - 1 (0) | 2022.01.08 |