- [Vue.js] 카카오 지도 API 동적 데이터 바인딩 가능한 오버레이(마커) 구현, n개 오버레이 일괄 제거2023년 01월 06일 14시 47분 28초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
1. 동적 데이터 바인딩 가능한 커스텀 오버레이(마커) 구현
카카오 지도 API를 이용해 서비스를 구현 중이었는데 마커에 동적인 데이터 바인딩이 필요했다. 또한 마우스 호버, 클릭 이벤트에 대해서도 각각 다른 애니메이션을 보여 주어야 했다. [그림 1]은 노멀 상태에서는 circle 애니메이션, hover 시에는 circle + spinner 애니메이션, clicked 시에는 spinner 애니메이션이 적용된 커스텀 오버레이이다.
[그림 1]을 구현하기 위해 HTMLElement를 이용했다. 카카오 지도 API에서는 Vue의 데이터 바인딩 처리가 되지 않았기 때문이다. 구현 코드는 다음과 같다.
1. template
<template> <div id="mapContainer"> <div id="map"></div> </div> </template>
2. script
<script> export default { components: {}, data() { return {} }, setup() {}, created() {}, mounted() { if (window.kakao && window.kakao.maps) { this.initMap() } else { const script = document.createElement('script') /* global kakao */ script.onload = () => kakao.maps.load(this.initMap) script.src = `http://dapi.kakao.com/v2/maps/sdk.js?autoload=false&appkey=${process.env.VUE_APP_KAKAOMAP_KEY}` document.head.appendChild(script) } }, unmounted() {}, methods: { // 현재 라우터의 게이트웨이(gid) 위경도 getGidLatLng() { for (let i = 0; this.$store.state.gid.length; i++) { if (this.$store.state.gid[i].gid === this.$store.state.routeInfo.gid) { return [ this.$store.state.gid[i].longitude, this.$store.state.gid[i].latitude ] } } }, initMap() { /* eslint-disable */ var positions = [ // 마커의 위치 new kakao.maps.LatLng(33.450705, 126.570677), new kakao.maps.LatLng(33.450936, 126.569477), new kakao.maps.LatLng(33.450879, 126.56994), new kakao.maps.LatLng(33.451393, 126.570738) ], selectedOverlay = null // 클릭한 오버레이를 담을 변수 var mapContainer = document.getElementById('map'), // 지도를 표시할 div mapOption = { center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표 level: 3 // 지도의 확대 레벨 } var map = new kakao.maps.Map(mapContainer, mapOption) // 지도를 생성합니다 // 커스텀 오버레이 클릭 이벤트 let customOverlayArr = [] function beforeClick(idx) { let content = document.createElement('a') content.classList.add('pin') let circle = document.createElement('div') circle.classList.add('circle') let spinnerLocation = document.createElement('div') spinnerLocation.classList.add('spinner-location') let spinner = document.createElement('div') spinner.classList.add('spinner') spinnerLocation.appendChild(spinner) let text = document.createElement('span') let textFirst = document.createElement('div') textFirst.appendChild(document.createTextNode(`(A)`)) let textSecond = document.createElement('div') textSecond.appendChild(document.createTextNode(`#${idx}`)) text.appendChild(textFirst) text.appendChild(textSecond) content.appendChild(circle) content.appendChild(spinnerLocation) content.appendChild(text) content.onclick = () => { selectedOverlay = positions[idx] var map = new kakao.maps.Map(mapContainer, mapOption) customOverlayArr = [] for (let i = 0; i < positions.length; i++) { if (selectedOverlay === positions[i]) { selectedOverlay = positions[i] customOverlayArr.push(afterClick(i)) } else { customOverlayArr.push(beforeClick(i)) } } console.log(customOverlayArr) for (let i = 0; i < customOverlayArr.length; i++) { // 커스텀 오버레이를 생성합니다 var customOverlay = new kakao.maps.CustomOverlay({ position: positions[i], content: customOverlayArr[i] }) // 커스텀 오버레이를 지도에 표시합니다 customOverlay.setMap(map) } selectedOverlay = positions[idx] console.log('선택된 오버레이 idx: ', idx) console.log('선택된 오버레이 위경도: ', selectedOverlay) } return content } function afterClick(value) { let content = document.createElement('a') content.classList.add('pin') let circle = document.createElement('div') circle.classList.add('circle-paused') let spinnerLocation = document.createElement('div') spinnerLocation.classList.add('spinner-location-clicked') let spinner = document.createElement('div') spinner.classList.add('spinner-clicked') spinnerLocation.appendChild(spinner) let text = document.createElement('span') let textFirst = document.createElement('div') textFirst.appendChild(document.createTextNode(`(A)`)) let textSecond = document.createElement('div') textSecond.appendChild(document.createTextNode(`#${value}`)) text.appendChild(textFirst) text.appendChild(textSecond) content.appendChild(circle) content.appendChild(spinnerLocation) content.appendChild(text) return content } for (let i = 0; i < positions.length; i++) { customOverlayArr.push(beforeClick(i)) } for (let i = 0; i < customOverlayArr.length; i++) { // 커스텀 오버레이를 생성합니다 var customOverlay = new kakao.maps.CustomOverlay({ position: positions[i], content: customOverlayArr[i] }) // 커스텀 오버레이를 지도에 표시합니다 customOverlay.setMap(map) } } } } </script>
3. style
<style> /* 커스텀 오버레이 */ .pin { cursor: pointer; width: 46px; height: 46px; border-radius: 50% 50% 50% 0; background-color: #cd0000; transform: rotate(-45deg); color: white; display: flex; justify-content: center; align-items: center; } .pin span { transform: rotate(45deg); flex-direction: column; line-height: 16px; } .pin span div:nth-child(1) { font-size: 12px; font-weight: bolder; } .pin span div:nth-child(2) { font-size: 16px; font-weight: bolder; } a { text-decoration: none; } .circle { border-radius: 50%; background-color: #cd0000; width: 50px; height: 50px; position: absolute; opacity: 0; animation: scaleIn 1s infinite cubic-bezier(0.36, 0.11, 0.89, 0.32); } @keyframes scaleIn { from { transform: scale(0.5, 0.5); opacity: 0.5; } to { transform: scale(2.5, 2.5); opacity: 0; } } a:hover .spinner-location { transform: rotate(60deg); } a:hover .spinner { border-radius: 50%; width: 20px; height: 20px; position: absolute; border: 3px solid rgba(205, 0, 0, 0.1); border-top: 3px solid #cd0000; animation: spin 0.5s linear infinite; top: 10px; } .spinner-paused { animation-play-state: paused; } .circle-clicked { border-radius: 50%; background-color: #cd0000; width: 0; height: 0; position: absolute; opacity: 0; animation: scaleIn 1s infinite cubic-bezier(0.36, 0.11, 0.89, 0.32); } .spinner-location-clicked { transform: rotate(60deg); } .spinner-clicked { border-radius: 50%; width: 20px; height: 20px; position: absolute; border: 3px solid rgba(205, 0, 0, 0.1); border-top: 3px solid #cd0000; animation: spin 0.5s linear infinite; top: 10px; } a:visited .spinner { border-radius: 50%; width: 20px; height: 20px; position: absolute; border: 3px solid rgba(205, 0, 0, 0.1); border-top: 3px solid #cd0000; animation: spin 0.5s linear infinite; top: 30px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* 이상 커스텀 오버레이 */ #mapContainer { width: 100%; height: 35%; } #map { width: 100vw; height: 100vh; } </style>
*2024-03-05 추가
2. 클릭 이벤트 시 커스텀 오버레이 중첩 렌더링 현상 해결
n개의 커스텀 오버레이를 반복문을 이용해 지도에 렌더링 하였는데 오버레이에 적용된 클릭 이벤트가 beforeClick(), afterClick()의 반환 값(커스텀 오버레이)에 따라서 각각 달리 렌더링 되어야 했다. 하지만 위 코드 만으로 시도하면 클릭할 때마다-기존 오버레이가 존재하는 상태에서-새로운 오버레이가 중첩되는 현상이 발생했다. 이 문제에 대한 해결은 오버레이 객체의 setMap(null) 메서드를 호출하여 기존 오버레이를 제거해 주고 새롭게 렌더링 하면 된다. 하지만 아래 코드와 같은 반복문에서 customOverlay 인스턴스를 생성하여 오버레이를 렌더링 한 후에 customOverlay.setMap(null)을 호출하면 가장 마지막에 생성된 오버레이만 제거되어 전체 오버레이를 제거할 수 없다.
for (let i = 0; i < customOverlayArr.length; i++) { var customOverlay = new kakao.maps.CustomOverlay({ position: positions[i], content: customOverlayArr[i] }) customOverlay.setMap(map) }
따라서 인스턴스를 생성할 때 배열에 push 하여 사용하고, 제거할 때도 역시 배열을 순회하면서 각 객체를 제거해주어야 한다. 아래 코드를 참조하자!
// (생략) let customOverlays = [] for (let i = 0; i < customOverlayArr.length; i++) { customOverlays.push( new kakao.maps.CustomOverlay({ position: positions[i], content: customOverlayArr[i] }) ) } customOverlays.forEach((el) => el.setMap(this.map)) // 마커(커스텀 오버레이) 렌더링 // (생략) marker.onclick = () => { customOverlays.forEach((el) => el.setMap(null)) // 기존 마커(커스텀 오버레이) 제거할 경우 // (생략) }
728x90반응형'언어·프레임워크 > Vue.js' 카테고리의 다른 글
다음글이 없습니다.이전글이 없습니다.댓글