Animation with 3D Module
This example demonstrates how to use the MaptilerAnimation class in combination with the @maptiler/3d module. It features a UFO model orbiting the Burj Khalifa, with controls to pause, play, adjust the playback rate, and advance frame by frame. The animation linearly interpolates the position (longitude, latitude, and altitude) of the UFO based on a set of defined keyframes.
NPM module setup
npm install --save @maptiler/sdk @maptiler/3d
import { Map, MapStyle, config, MaptilerAnimation } from "@maptiler/sdk";
import { Layer3D } from "@maptiler/3d";
import "@maptiler/sdk/dist/maptiler-sdk.css";
config.apiKey = "ivHgEpo8G3yNUmHYHp61";
const map = new Map({
container: "map",
style: MapStyle.STREETS.DARK,
hash: false,
geolocateControl: false,
navigationControl: false,
terrain: true,
bearing: -30,
maxPitch: 60,
minPitch: 60,
pitch: 60,
zoom: 15.3,
maxZoom: 15.3,
minZoom: 15.3,
center: [55.273056, 25.199267],
});
function getElementsByIds(ids) {
return ids.map((id) => document.getElementById(id));
}
map.on("load", async function () {
const animation = new MaptilerAnimation({
keyframes: getKeyframes(),
duration: 2500,
iterations: Infinity,
});
const layer3D = new Layer3D("3d-scene");
map.addLayer(layer3D);
await layer3D.addMeshFromURL(
"khalifa",
"https://docs-media.maptiler.com/docs/models/burj_khalifa/scene.gltf",
{
lngLat: [55.2743164, 25.1973882],
heading: -2,
scale: 0.013,
},
);
await layer3D.addMeshFromURL(
"ufo",
"https://docs-media.maptiler.com/docs/models/ufo/scene.gltf",
{
lngLat: [55.273, 25.197],
altitude: 500,
scale: 10,
},
);
animation.addEventListener("timeupdate", (e) => {
map.setBearing(map.getBearing() + 0.1);
const ufo = layer3D.getItem3D("ufo");
if (ufo) {
ufo.modify({
lngLat: [e.props.lng, e.props.lat],
altitude: e.props.altitude,
});
}
});
animation.addEventListener("playbackratechange", (e) => {
const playbackRateElement = document.getElementById("playbackRate");
if (!playbackRateElement) return;
playbackRateElement.innerText = e.playbackRate.toFixed(1) + "x";
});
const [playButton, pauseButton, fasterButton, slowerButton, frameAdvanceButton] =
getElementsByIds(["play", "pause", "faster", "slower", "frame-advance"]);
playButton?.addEventListener("click", () => {
animation.play();
});
pauseButton?.addEventListener("click", () => {
animation.pause();
});
fasterButton?.addEventListener("click", () => {
const currentSpeed = animation.getPlaybackRate();
animation.setPlaybackRate(currentSpeed + 0.2);
});
slowerButton?.addEventListener("click", () => {
const currentSpeed = animation.getPlaybackRate();
animation.setPlaybackRate(currentSpeed - 0.2);
});
frameAdvanceButton?.addEventListener("click", () => {
animation.setCurrentDelta(animation.getCurrentDelta() + 0.01);
});
animation.play();
});
function getKeyframes() {
return [
{
delta: 0,
easing: "ElasticInOut",
props: {
lng: 55.273,
lat: 25.197,
altitude: 300,
},
},
{
delta: 0.33333,
easing: "ElasticInOut",
props: {
lng: 55.27439873444448,
lat: 25.198719685352263,
altitude: 250,
},
},
{
delta: 0.666666,
easing: "ElasticInOut",
props: {
lng: 55.2753633312754,
lat: 25.195489657613702,
altitude: 350,
},
},
{
delta: 1.0,
easing: "ElasticInOut",
props: {
lng: 55.273,
lat: 25.197,
altitude: 300,
},
},
];
}
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>MaptilerAnimation with MapTiler 3D Module</title>
<link href='style.css' rel='stylesheet' />
</head>
<body>
<div class='controls-container'>
<div class='controls'>
<button id='play'>Play</button>
<button id='pause'>Pause</button>
<button id='faster'>Faster</button>
<button id='slower'>Slower</button>
<button id='frame-advance'>Frame Advance</button>
<div id='playbackRate' class='padded'>1.0x</div>
</div>
<div id='keyframeName' class='padded' style='display: none'></div>
</div>
<div id='map'></div>
<script type="module" src="./main.js"></script>
</body>
</html>
body {
margin: 0;
padding: 0;
height: 100vh;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.controls-container {
position: absolute;
top: 16px;
left: 16px;
gap: 8px;
z-index: 1;
}
.controls {
display: flex;
}
.padded,
button {
display: inline-flex;
align-items: center;
border-radius: 8px;
background-color: white;
padding: 8px;
border: 1px solid #ccc;
font-size: 16px;
margin-right: 4px;
}
button {
cursor: pointer;
}
Related examples
Animations and Animated Routes
ExamplesAnimates a path or route on the map based on keyframes or GeoJSON data.
Customize camera animations
ExamplesCustomize map camera animations using the AnimationOptions property.
Animate routes dynamic camera
ExamplesAnimate multidimensional routes with a dynamic camera that follows the journey in real-time.