Animate a custom marker on appearance
Add animation effects to a Marker
as it displays on the map to create a pop-in & wobble effect.
Note
Markers
in Mapbox GL JS are placed on the map using CSS transform
properties, so you can't use transform
properties to apply animation effects directly on the marker element. To work around this and enable robust animation possibilities, you can nest an 'inner' div within your marker and apply your custom CSS transforms to that element.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Animate a custom marker on appearance</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.9.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.9.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<style>
button {
position: absolute;
margin: 20px;
font:
bold 12px/20px 'Helvetica Neue',
Arial,
Helvetica,
sans-serif;
background-color: #3386c0;
color: #fff;
z-index: 1;
border: none;
width: 200px;
cursor: pointer;
padding: 10px 20px;
border-radius: 3px;
}
button:hover {
background-color: #4ea0da;
}
/* Style the marker div as a circle with a background image */
.marker {
background-image: url('https://docs.mapbox.com/mapbox-gl-js/assets/coffee-cup-marker.svg');
background-size: contain;
background-position-x: 50%;
background-repeat: no-repeat;
border-radius: 50%;
cursor: pointer;
transition-property: width, height;
transition-duration: 0.1s;
transition-timing-function: linear;
/* Define Marker Animation */
animation:
pop-in 0.3s ease-out forwards,
wobble 1.5s ease-in-out 0.3s forwards;
transform-origin: bottom center;
}
/* Create a css triangle and add to the bottom of the marker for the marker point */
.marker::before {
content: '';
width: 0;
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #365874;
display: block;
position: absolute;
bottom: -5px;
left: 50%;
transform: translateX(-50%);
}
/* Keyframe animation for pop-in */
@keyframes pop-in {
0% {
width: 0px;
height: 0px;
opacity: 0;
box-shadow: none;
}
70% {
width: 55px;
height: 55px;
opacity: 1;
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.4); /* Add a subtle shadow */
}
85% {
width: 48px;
height: 48px;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.3); /* Shrink shadow with wobble */
}
100% {
width: 50px;
height: 50px;
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); /* Final shadow size */
}
}
/* Keyframe animation for slow wobble after pop-in */
@keyframes wobble {
0% {
transform: rotate(0deg);
}
20% {
transform: rotate(2.5deg);
}
50% {
transform: rotate(-2deg);
}
65% {
transform: rotate(1deg);
}
100% {
transform: rotate(0deg);
}
}
</style>
<div id="map"></div>
<button id="addMarker" onclick="addMarker()">Add Marker to the Map</button>
<script>
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/streets-v12',
center: [-73.21218, 44.47579],
zoom: 11
});
// Create the marker element. To allow CSS transform properties to animate
// the marker styles, we create an inner div which will receive the animation transforms
// while GL JS continues to operate on the outer marker div apply transform/translate to correctly
// position the marker on the map
const markerEl = document.createElement('div');
const markerInner = document.createElement('div');
markerInner.className = 'marker';
markerEl.appendChild(markerInner);
// declare Marker
let marker;
let markerRendered = false;
// eslint-disable-next-line no-unused-vars
function addMarker() {
const btn = document.getElementById('addMarker');
if (!markerRendered) {
// Add marker to the map
marker = new mapboxgl.Marker(markerEl)
.setLngLat([-73.21218, 44.47579])
.addTo(map);
btn.textContent = 'Reset';
markerRendered = true;
} else {
marker.remove();
btn.textContent = 'Add Marker to the Map';
markerRendered = false;
}
}
</script>
</body>
</html>
This code snippet will not work as expected until you replace
YOUR_MAPBOX_ACCESS_TOKEN
with an access token from your Mapbox account.import React, { useEffect, useRef, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
const MapboxExample = () => {
const mapContainerRef = useRef();
const mapRef = useRef();
const markerRef = useRef();
const [showMarker, setShowMarker] = useState(false);
function handleClick() {
if (!showMarker) {
// Create the marker element. To allow CSS transform properties to animate
// the marker styles, we create an inner div which will receive the animation transforms
// while GL JS continues to operate on the outer marker div apply transform/translate to correctly
// position the marker on the map
const markerEl = document.createElement('div');
const markerInner = document.createElement('div');
markerInner.className = 'marker marker-pop';
markerEl.appendChild(markerInner);
// Add marker to the map
markerRef.current = new mapboxgl.Marker(markerEl)
.setLngLat([-73.21218, 44.47579])
.addTo(mapRef.current);
setShowMarker(true);
} else {
markerRef.current.remove();
setShowMarker(false);
}
}
useEffect(() => {
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
mapRef.current = new mapboxgl.Map({
container: 'map',
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/streets-v12',
center: [-73.21218, 44.47579],
zoom: 11
});
// Clean up by removing marker and map on component unmount
return () => {
markerRef.remove();
mapRef.current.remove();
};
}, []);
return (
<>
<button
style={{
position: 'absolute',
margin: '20px',
backgroundColor: '#3386c0',
color: '#fff',
zIndex: 1,
border: 'none',
width: '200px',
cursor: 'pointer',
padding: '10px 20px',
borderRadius: '3px'
}}
onClick={handleClick}
>
{showMarker ? 'Reset' : 'Show Marker on the Map'}
</button>
<div id="map" ref={mapContainerRef} style={{ height: '100%' }}></div>;
<style>
{/* These style should be imported or stored elswhere in your app structure.
For the purposes of this example they are included locally */}
{` /* Style the marker div as a circle with a background image */
.marker {
background-image: url('https://docs.mapbox.com/mapbox-gl-js/assets/coffee-cup-marker.svg');
background-size: contain;
background-position-x: 50%;
background-repeat: no-repeat;
border-radius: 50%;
cursor: pointer;
transition-property: width, height;
transition-duration: .1s;
transition-timing-function: linear;
/* Define Marker Animation */
animation: pop-in 0.3s ease-out forwards, wobble 1.5s ease-in-out 0.3s forwards;
transform-origin: bottom center;
}
/* Create a css triangle and add to the bottom of the marker for the marker point */
.marker::before {
content: '';
width: 0;
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #365874;
display: block;
position: absolute;
bottom: -5px;
left: 50%;
transform: translateX(-50%)
}
/* Keyframe animation for pop-in */
@keyframes pop-in {
0% {
width: 0px;
height: 0px;
opacity: 0;
box-shadow: none;
}
70% {
width: 55px;
height: 55px;
opacity: 1;
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.4); /* Add a subtle shadow */
}
85% {
width: 48px;
height: 48px;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.3); /* Shrink shadow with wobble */
}
100% {
width: 50px;
height: 50px;
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); /* Final shadow size */
}
}
/* Keyframe animation for slow wobble after pop-in */
@keyframes wobble {
0% {
transform: rotate(0deg);
}
20% {
transform: rotate(2.5deg);
}
50% {
transform: rotate(-2deg);
}
65% {
transform: rotate(1deg);
}
100% {
transform: rotate(0deg);
}
}
`}
</style>
</>
);
};
export default MapboxExample;
This code snippet will not work as expected until you replace
YOUR_MAPBOX_ACCESS_TOKEN
with an access token from your Mapbox account.Was this example helpful?