[Vue.js] Implementing Vue Kakao Maps 03 - Implementing Overlay Popup
Implementing an Overlay popup for Kakao Maps in Vue.js.
[Vue.js] Vue Kakao Map Implementation 03 - Implementing the Overlay popup
project Structure
Project Structure[ROOT]
+- public
| +- index.html
+- src
+- components/map
| +- overlay
| +- index.js (+)
|Β
| +- KakaoMap.vue (E)
| +- marker-handler.js
|Β
+- service
| +- api.js
| +- app.vue (E)
+- App.vue (E)
+- main.js
- overlay/index.js - Class responsible for the overlay
- App.vue - Inserts the element to be used for overaly into KakaoMap
- KakaoMap.vue - defines the element for overlay as slot
In Section1, I attach the complete source code.
for a detailed description, go to Section 2.
1. Source Code
src/components/map/overlay/index.js (+)
Class responsible for Overlay popup (new)
.../map/overlay/index.jsconst kakao = window.kakao;
class KakaoOverlay {
constructor(vueMap, content) {
this.vueMap = vueMap;
this.content = content;
// init instance
this.instance = new kakao.maps.CustomOverlay({
map: null,
clickable: false,
content: content,
position: null,
xAnchor: 0.5,
yAnchor: 1,
zIndex: 3,
});
}
showAt(lat, lng) {
// show at (lat, lng )
console.log("overlay show!!", lat, lng);
this.instance.setMap(this.vueMap.mapInstance);
const pos = new kakao.maps.LatLng(lat, lng);
this.instance.setPosition(pos);
}
} hide() {
this.instance.setMap(null);
}
}
export default KakaoOverlay;
src/App.vue
define the element to be used for the overlay
src/App.vue<template> <div>
<div>
<h3>kakao map demo(center, level)</h3>
<div class="controll">
<button @click="zoom(-1)">
<span class="material-icons"> zoom_in </span>
</button>
<button @click="zoom(1)"> <span class="material-icons"> zoom_in</span> </button>
<span class="material-icons"> zoom_out </span>
</button>
</div>
<div class="map-area"> <div class="harbors">
<div class="harbors">
<div
class="harbor"
v-for="hbr in harbors"
:key="hbr.seq"
@click="showOnMap(hbr)"
:class="{ active: hbr === activeHarbor }"
>
<h4>{{ hbr.place }}</h4>
</div>
</div>
<KakaoMap ref="kmap" class="kmap" :options="mapOption">
<div class="overlay-popup" ref="harborOverlay" slot="overlay">
<div v-if="overlayHarber">
<h3>{{ overlayHarber.place }}</h3>
<div class="addr">{{ overlayHarber.addr }}</div>
<a class="close" href="#" @click.prevent="closeOverlay()"
><span class="material-icons"> close </span></a
>
</div>
</div>
</KakaoMap>
</div>
</div>
</template>
<script> <script>
import KakaoMap from "./components/map/KakaoMap.vue";
import api from "./service/api";
import MarkerHandler from "./components/map/marker-handler";
import KakaoOverlay from "./components/map/overlay";
export default {
components: {
KakaoMap,
},
data() {
return {
mapOption: {
center: {
lat: 33.450701,
lng: 126.570667,
},
level: 8,
},
harbors: [], // empty
markers: null, // marker handler!
activeHarbor: null, // selected harbor!
overlay: null, // overlay instance
overlayHarber: null, // harbor to show in overlay
};
},
mounted() {
const vueKakaoMap = this.$refs.kmap;
this.markers = new MarkerHandler(vueKakaoMap, {
markerClicked: (harbor) => {
console.log("[clicked ]", harbor);
// this.activeHarbor = harbor;
this.showOnMap(harbor);
// on marker click
this.overlayHarber = harbor;
this.overlay.showAt(harbor.lat, harbor.lng);
},
});
this.overlay = new KakaoOverlay(vueKakaoMap, this.$refs.harborOverlay);
api.harbor.all((res) => {
console.log("[List of harbors]", res.harbors);
this.harbors = res.harbors;
// create markers
this.markers.add(this.harbors, (harbor) => {
return { lat: harbor.lat, lng: harbor.lng };
});
});
},
methods: {
zoom(delta) {
// delta : 1 or -1
// console.log("[delta]", delta);
const level = Math.max(3, this.mapOption.level + delta); // min level 3
this.mapOption.level = level;
// console.log(this.mapOption.level);
},
showOnMap(harbor) {
this.activeHarbor = harbor;
// console.log("[center]", harbor);
this.mapOption.center = {
lat: harbor.lat,
lng: harbor.lng,
};
},
closeOverlay() {
this.overlay.hide();
},
},
};
</script> </script
<style lang="scss">
button {
border: 1px solid transparent;
padding: 6px;
background-color: #efefefdd;
border-radius: 6px;
&:hover {
background-color: #ddd;
border-color: #ddd;
cursor: pointer;
}
&:active {
background-color: #aaa;
border-color: #aaa;
}
}
.map-area {
display: flex;
.harbors {
.harbor {
padding: 10px;
border: 1px solid transparent;
&:hover {
background-color: aliceblue;
border-color: #6a9dff;
cursor: pointer;
}
&:active {
background-color: rgb(166, 197, 224);
border-color: #4471c5;
}
&.active {
background-color: rgb(253, 229, 150);
border-color: rgb(211, 173, 3);
}
h4 {
margin: 0;
}
}
}
.kmap {
flex: 1 1 auto;
.overlay-popup {
background-color: #ffffffcc;
box-shadow: 0 0 8px #0000004d, 0 0 1px 2px #00000022;
max-width: 200px;
min-width: 160px;
position: absolute;
bottom: 44px;
left: 50%;
transform: translateX(-50%);
h3 {
margin: 0;
padding: 8px;
padding-right: 24px;
background-color: #ed4215;
color: white;
font-weight: 400;
font-size: 16px;
}
.addr {
padding: 8px;
white-space: break-spaces;
}
.close {
color: black;
position: absolute;
top: 0;
left: 100%;
transform: translate(-50%, -50%);
background-color: white;
border-radius: 100%;
line-height: 0;
padding: 6px;
box-shadow: 0 0 6px #0000004d;
}
}
}
}
</style>
src/components/map/KakaoMap.vue
- define named slot(<slot name="overlay"/>)
- use panTo method instead of setCenter to move the center coordinate
KakaoMap.vue<template>
<div ref="map">
<slot name="overlay"></slot>
</div>
</template>
<script> </script
let kakao = window.kakao;
export default {
props: ["options"],
data() {
return {
mapInstance: null,
};
},
mounted() {
kakao = kakao || window.kakao;
console.log(this.$refs.map); // should be not null
var container = this.$refs.map;
const { center, level } = this.options;
this.mapInstance = new kakao.maps.Map(container, {
center: new kakao.maps.LatLng(center.lat, center.lng),
level,
});
},
watch: {
"options.level"(cur, prev) {
console.log(`[LEVEL CHANGED] ${prev} => ${cur}`); // for testing
this.mapInstance.setLevel(cur);
},
"options.center"(cur) {
this.mapInstance.panTo(new kakao.maps.LatLng(cur.lat, cur.lng));
// pan smoothly
},
},
};
</script>
<style>
.kmap {
height: 600px;
}
</style>
2. Description
When implementing the CustomOverlay provided by the Kakao Maps API in a vue.js-based project, there are some issues that need to be solved.
the image below shows an example page of overlay usage

the corresponding code is shown below.
JAVASCRIPT// The content to be displayed in the custom overlay can be either an HTML string or a document element
var content = '<div class="customoverlay">' +
' <a href="https://map.kakao.com/link/map/11394059" target="_blank">' +'
' <span class="title">구구의야구공원</span>' +
' </a>' +
'</div>';
assembling overlay HTML from strings like the above is something you should avoid in SPA projects like vue.js.
here, we're going to use the <slot/>
provided by vue.js to render the overlay screen.
<slot /> Using slot />
inside the KakaoMap
component, paste the html we want to use in the overlay like below.
App.vue<template> <div
<div> <div>
....
<div class="map-area">
....
<KakaoMap ref="kmap" class="kmap" :options="mapOption">
<div class="overlay-popup" ref="harborOverlay" slot="overlay">
<div> <h3>Harbor name location
<h3>Harbor name location</h3>
<div class="addr">Harbor Address Location</div>
<a href="#">Close</a>
</div>
</div>
</KakaoMap>
</div>
</div>
</template>
- prepared the title (
h3
) and address (.addr
) to show the port information. - we also added a function to close the overlay by pressing the close button.
- ref - refer to the element with ref to pass it when creating the overlay
- slot="overlay" - Insert the slot named "overlay" inside the KakaoMap component.
now define the corresponding slot inside KakaoMap.vue
.
KakaoMap.vue<template> <div
<div ref="map"></slot
<slot name="overlay"></slot>
</div>
</template>
when the application starts correctly, the slot position will be replaced with the element of the overlay that you inserted in App.vue, as shown below.
KakaoMap.vue<template> <div ref="map
<div ref="map">
<div class="overlay-popup"> <div class="overlay-popup">
<div> <div>
<h3>Port name location</h3>
<div class="addr">Port address location</div>
<a href="#">Close</a>
</div>
</div>
</div>
</template>
<div ref="map">
is a placeholder where the Kakao Maps library inserts map-related images. The elements of the overlay exist as children of the placehoder, but they are moved to a different place when the overlay window is opened.
The click event that you want to assign to the close button in App.vue still works even though the element in the overlay is moved elsewhere.
Create the KakaoOverlay class
in the official documentation, the code to bring up the overlay is given below.
Using javascript
the class KakaoOverlay is where we hide the specific overlay creation process and add additional functionality.
.../map/overlay/index.jsconst kakao = window.kakao;
class KakaoOverlay {
constructor(vueMap, content) {
this.vueMap = vueMap;
this.content = content;
// init instance
this.instance = new kakao.maps.CustomOverlay({
...
});
}
showAt(lat, lng) {
// renders the overlay at the given location
}
hide() {
// closes the overlay if null is specified, as shown below
this.instance.setMap(null);
}
}
export default KakaoOverlay;
- Regular js files, not Vue files
- vueMap - an instance of the file
KakaoMap.vue
(VueComponent) - content - the on-screen content of the overlay. the official documentation mostly shows examples of assembling HTML directly, but you can also pass a DOM element to this location. in this case, we pass the overlay element that we injected from
App.vue
.
The KakaoOverlay class is used like below in App.vue
.
App.vue<template>
</template>
<script>
...
// (1) Import
import KakaoOverlay from "./components/map/overlay";
export default {
data() {
return {
...
overlay: null, // (2) for reference to overlay instances
}
},
mounted() {
const vueKakaoMap = this.$refs.kmap;
this.markers = ....
// (3) Pass in a map component and an overlay element
this.overlay = new KakaoOverlay(vueKakaoMap, this.$refs.harborOverlay);
....
},
}
</script>
- (1) Import the KakaoOverlay class
- (2) Variable to reference the overlay instance
- (3) Call the constructor passing in the map component (an instance of KakaoMap.vue) and the element reference to use as the content of the overlay
now, in the KakaoOverlay class, we prepare the actual overlay as shown below.
.../map/overlay/index.jsconst kakao = window.kakao;
class KakaoOverlay {
constructor(vueMap, content) {
this.vueMap = vueMap;
this.content = content;
// init instance
this.instance = new kakao.maps.CustomOverlay({
map: null,
clickable: false,
content: content,
xAnchor: 0.5,
yAnchor: 1,
zIndex: 3,
});
}
....
}
export default KakaoOverlay;
- When using the CustomOverlay class provided by the Kakao Maps library, we specify null for the map property, so we don't display it on the screen yet.
- xAnchor: 0.5 - coordinates to be centered on the left and right width of the overlay screen
- yAnchor: 1 - position the coordinates at the y-axis of the overlay screen's height
- zIndex: You can also adjust this value to make certain overlays appear on top of other overlays.
note that the coordinates don't actually move (it's the overlay that does)
TXT(xAhcnor: 0.5, yAnchor: 1)
BBBBB
BBBBB
BBBBB
+
(xAhcnor: 0.5, yAnchor: 0)
+ (xAhcnor: 0.5)
BBBBB
BBBBB
BBBBB
(xAhcnor: 0, yAnchor: 0)
+0
BBBBB
BBBBB
BBBBB
(xAhcnor: 0.5, yAnchor: 0.5)
BBBBB
BB+BB
BBBBB
click the ### marker
now in App.vue, when you click on the marker, we'll pass in the location of the harbor to bring up the overlay.
App.vueexport default {
mounted() {
....
this.markers = new MarkerHandler(vueKakaoMap, {
markerClicked: (harbor) => {
....
this.showOnMap(harbor);
// show overlay on marker click
this.overlay.showAt(harbor.lat, harbor.lng);
},
});
this.overlay = ...
....
},
- call showAt(lat, lng) to show the overlay
Implement the showAt method in the KakaoOverlay class.
.../map/overlay/index.jsconst kakao = window.kakao;
class KakaoOverlay {
constructor(vueMap, content) {
this.vueMap = vueMap;
this.content = content;
this.instance = ....
....
}
showAt(lat, lng) {
this.instance.setMap(this.vueMap.mapInstance);
const pos = new kakao.maps.LatLng(lat, lng);
this.instance.setPosition(pos);
}
}
- this.instance - Overlay object created with kakao.mpas.CustomOverlay
- this.vueMap.mapInstance - Map object created with kakao.maps.Map
- positioned with setMap, setPosition and rendered to the map
Overly Design
In App.vue, design the overlay like below.
App.vue<style lang="scss"><style lang="scss"></style
....
.map-area {
.kmap {
....
.overlay-popup {
background-color: #ffffffcc;
box-shadow: 0 0 8px #0000004d, 0 0 1px 2px #00000022;
max-width: 200px;
min-width: 160px;
h3 {
margin: 0;
padding: 8px;
padding-right: 24px;
background-color: #ed4215;
color: white;
font-weight: 400;
font-size: 16px;
}
.addr {
padding: 8px;
white-space: break-spaces;
}
}
}
}
</style>
when you click the marker, an overlay appears on top of the coordinates, as shown below.

the overlay is rendered in the desired position, but it overlaps the marker and doesn't look good.
add a style to the .overlay-popup
that modifies the position.
App.vue<style lang="scss">
....
.map-area {
.kmap {
....
.overlay-popup {
....
position: absolute;
bottom: 44px;
left: 50%;
transform: translateX(-50%);
h3 {
...
}
.addr {
...
}
}
}
}
</style>
- bottom: 44px - spacing by the height of the base marker so they don't overlap
- left, transform - centered alignment
See the video for the close button design https://youtu.be/2wEKmcdVWmA
Fill the Overlay Content
we want to show the port information in the overlay when the marker is clicked.
define a variable to represent the port to show in the overlay and bind the port name and address to the overay element.
App.vue<template
....
<KakaoMap ...>
<div class="overlay-popup" ...> <div v-if="overlayHarber"><!-- (3) Rendering --> <!!
<div v-if="overlayHarber"><!-- (3) Rendering -->
<h3>{{ overlayHarber.place }}</h3>
<div class="addr">{{ overlayHarber.addr }}</div>
<a class="close" href="#" @click.prevent="closeOverlay()"
><span class="material-icons"> close </span></a
>
</div>
</div>
</KakaoMap>
</template>
<script> <script
export default {
data() {
return {
...
overlay: null,
overlayHarber: null, // (1) Ports to show in the overlay
}
},
mounted() {
....
this.markers = new MarkerHandler(vueKakaoMap, {
markerClicked: (harbor) => {
...
// on marker click
this.overlayHarber = harbor; // (2) bind the clicked harbor
this.overlay.showAt(harbor.lat, harbor.lng);
},
});
....
},
methods: {
...
closeOverlay() {
this.overlay.hide(); // (4) Close the overlay
}
}
}
</script>
- (1, 2) Bind the corresponding harbor on marker click to a variable (this.overlayHarber = harbor)
- (3) Render
overlayHarbor
to the overlay element in the template - (4) Close the overlay when the close button is clicked
finally, implement the method hide() in the KakaoOverlay
class.
.../map/overlay/index.jsconst kakao = window.kakao;
class KakaoOverlay {
...
showAt(lat, lng) { ... }
hide() {
this.instance.setMap(null);
}
}
- if you want to close the overlay, set the map object to null - CustomOverlay.setMap(null)