Skip to content

Instantly share code, notes, and snippets.

@chriswhong
Last active October 18, 2023 09:37
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chriswhong/8977c0d4e869e9eaf06b4e9fda80f3ab to your computer and use it in GitHub Desktop.
Save chriswhong/8977c0d4e869e9eaf06b4e9fda80f3ab to your computer and use it in GitHub Desktop.
Clickable Markers in MapboxGL

This example shows how to extend mapboxGL's Marker class to add custom functionality on click.

Why?

MapboxGL has a very convenient Marker class that can be used to quickly get markers on the map with a few lines of code (versus the more complex method of adding sources and layers). They behave a bit differently from the rest of the map features because they are actually HTML elements overlaid on the map canvas.

The stock markers are great, and they are SVG so you can color them by passing in a color option! However, the only interactivity you can easily set up is a popup. When you google 'clickable markers', the examples you find are all using symbol layers with queryRenderedFeatures.

I wanted to trigger navigation in a single page app using the stock Markers and determined a simple extension of the class would help me accomplish this. You can also see these markers in action at https://paintthetown.chriswhong.com

How it works

Marker already has an internal method _onMapClick which is used by the popup functionality. The extension does two things:

  1. Adds the method onMapClick which takes a function and can be chained with other methods.

  2. Overrides _onMapClick to trigger _handleClick if it exists.

Some CSS is used to set the pointer to a cursor on hover, and to change the fill.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.css" rel="stylesheet" />
<style>
html, body, #mapContainer {
height: 100%;
margin: 0;
}
/* Change the cursor to a pointer on hover so the user knows it's clickable */
.mapboxgl-marker:hover {
cursor: pointer;
}
/* darken the marker on hover */
.mapboxgl-marker:hover svg > g > g:nth-child(2) {
fill: #7993a5;
}
#info-box {
position: absolute;
z-index: 10;
left: 7px;
font-family: sans-serif;
background: #dedede;
padding: 12px;
border-radius: 8px;
font-size: 2rem;
border: 1px solid #969696;
bottom: 34px;
}
</style>
</head>
<body>
<div id="mapContainer"></div>
<div id="info-box">Click a City</div>
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.js'></script>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiY3dob25nIiwiYSI6IjAyYzIwYTJjYTVhMzUxZTVkMzdmYTQ2YzBmMTM0ZDAyIn0.owNd_Qa7Sw2neNJbK6zc1A';
const map = new mapboxgl.Map({
container: 'mapContainer',
style: 'mapbox://styles/mapbox/light-v9',
center: [-100.52, 37.67],
zoom: 3.05,
pitch: 36,
hash: true,
});
// Add zoom and rotation controls to the map.
map.addControl(new mapboxgl.NavigationControl());
// extend mapboxGL Marker so we can pass in an onClick handler
class ClickableMarker extends mapboxgl.Marker {
// new method onClick, sets _handleClick to a function you pass in
onClick(handleClick) {
this._handleClick = handleClick;
return this;
}
// the existing _onMapClick was there to trigger a popup
// but we are hijacking it to run a function we define
_onMapClick(e) {
const targetElement = e.originalEvent.target;
const element = this._element;
if (this._handleClick && (targetElement === element || element.contains((targetElement)))) {
this._handleClick();
}
}
};
const cities = [
{
name: 'New York',
coordinates: [ -73.969145, 40.669116 ],
},
{
name: 'Washington, D.C.',
coordinates: [ -77.047119, 38.856820 ],
},
{
name: 'Chicago',
coordinates: [ -87.662659, 41.865470 ],
},
{
name: 'Seattle',
coordinates: [ -122.266846, 47.591346 ],
},
{
name: 'St. Louis',
coordinates: [ -90.304871, 38.634036 ],
},
{
name: 'Houston',
coordinates: [ -95.377808, 29.754840 ],
},
];
cities.forEach((city) => {
new ClickableMarker()
.setLngLat(city.coordinates)
.onClick(() => { // onClick() is a thing now!
document.getElementById('info-box')
.innerHTML = `You clicked ${city.name}!`;
})
.addTo(map);
});
</script>
</body>
</html>
@FredodaFred
Copy link

What would the TypeScript version of this look like?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment