Fly to a Location Based on Scroll Position in Maptoolkit Maps JS
Scroll-linked maps connect the page’s scroll position to the map camera by listening to the scroll event and mapping the scroll offset to a set of predefined locations. As the user scrolls through the page content, the map flies or eases to corresponding coordinates. Use this pattern for narrative journalism, travel guides, or any long-form content that uses a map to illustrate locations as the reader progresses through the story.
const API_KEY = 'YOUR_API_KEY';
const map = new maptoolkit.Map({
container: 'map',
apiKey: API_KEY,
style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
center: [11.39085, 47.27574],
zoom: 12,
interactive: false,
attributionControl: { compact: false }
});
const steps = document.querySelectorAll('.step');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const el = entry.target;
const [lng, lat] = el.dataset.center.split(',').map(Number);
const zoom = Number(el.dataset.zoom);
map.flyTo({ center: [lng, lat], zoom, essential: true });
}
});
}, { threshold: 0.5 });
steps.forEach(step => observer.observe(step));<!DOCTYPE html>
<html lang="en">
<head>
<title>Fly to a Location Based on Scroll Position - Maptoolkit Maps JS</title>
<meta property="og:description" content="Move the map camera as the user scrolls down the page." />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
<style>
body { margin: 0; padding: 0; }
#map { position: sticky; top: 0; width: 100%; height: 100vh; }
#story {
position: relative;
z-index: 1;
pointer-events: none;
}
.step {
height: 100vh;
display: flex;
align-items: center;
padding: 0 40px;
}
.step-inner {
background: rgba(255,255,255,0.9);
padding: 20px;
border-radius: 8px;
max-width: 300px;
pointer-events: auto;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="story">
<div class="step" data-center="11.39085,47.27574" data-zoom="12">
<div class="step-inner"><h3>Innsbruck</h3><p>Capital of Tyrol, nestled in the Alps at 574 m elevation.</p></div>
</div>
<div class="step" data-center="13.0550,47.8095" data-zoom="13">
<div class="step-inner"><h3>Salzburg</h3><p>Birthplace of Mozart and UNESCO World Heritage city.</p></div>
</div>
<div class="step" data-center="16.3738,48.2082" data-zoom="13">
<div class="step-inner"><h3>Vienna</h3><p>Austria's capital and cultural heart on the Danube.</p></div>
</div>
</div>
<script>
const API_KEY = 'YOUR_API_KEY';
const map = new maptoolkit.Map({
container: 'map',
apiKey: API_KEY,
style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
center: [11.39085, 47.27574],
zoom: 12,
interactive: false,
attributionControl: { compact: false }
});
const steps = document.querySelectorAll('.step');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const el = entry.target;
const [lng, lat] = el.dataset.center.split(',').map(Number);
const zoom = Number(el.dataset.zoom);
map.flyTo({ center: [lng, lat], zoom, essential: true });
}
});
}, { threshold: 0.5 });
steps.forEach(step => observer.observe(step));
</script>
</body>
</html>Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.
How it works
The page’s scroll position drives the camera. Each chapter element has a location attached, and as it enters the viewport the map flies to it, which is the pattern behind most scroll-driven map stories.
scroll fires very often, so the handler has to be cheap. Calling flyTo on every event
queues animations that fight each other; the usual fix is to track which chapter is active and
only move when that changes.
An IntersectionObserver is the better mechanism than reading scroll offsets: the browser
reports when an element enters the viewport, with no per-frame maths and no layout thrash from
reading element positions during scroll.
Mark these animations essential: true only if the movement carries meaning, since it also
overrides a user’s reduced-motion preference.
Next steps
The map is half of a scroll story; the writing is the other half, and each section needs to earn its move. The next step is usually deciding what changes besides the camera, since highlighting the feature being discussed does more than moving to it.
Mobile deserves a separate decision rather than the same layout narrower. A map that scrolls past on a phone often works better pinned above the text, or replaced by a static image from the Static Maps API.