An open-source, scroll-driven storytelling template using MapLibre GL JS. Create beautiful, interactive map-based stories without proprietary dependencies or API keys.
- Free and Open Source: No API keys required for basic usage
- Scroll-Driven Navigation: Smooth transitions between map locations as you scroll
- Multiple Free Map Styles: Choose from various free tile providers (Carto, OpenFreeMap, etc.)
- Customizable Chapters: Define your story with chapters containing text, images, and map views
- 3D Terrain Support: Optional terrain visualization with MapLibre's terrain capabilities
- Inset Minimap: Optional overview map showing current location
- Responsive Design: Works on desktop and mobile devices
- Layer Control: Show/hide map layers as chapters change
- Rotate Animation: Optional slow rotation effect for dramatic views
- Location Helper: Built-in tool to capture map coordinates
View the demo by opening index.html in a web browser with a local development server.
- A text editor (VS Code, Sublime Text, etc.)
- A local development server (Live Server extension for VS Code, Python's
http.server, etc.) - A web browser
-
Clone or download this repository
git clone https://github.com/opengeos/maplibre-gl-storymaps.git cd maplibre-gl-storymaps -
Start a local development server
Using Python:
python -m http.server 8000
Or using Node.js:
npx serve .Or use the Live Server extension in VS Code.
-
Open in browser
Navigate to
http://localhost:8000(or the port shown by your server). -
Customize your story
Edit
config.jsto add your own content and locations.
All story configuration is done in config.js. Here's the structure:
var config = {
// Map style URL (required)
style: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
// Show markers at chapter locations
showMarkers: true,
markerColor: '#3FB1CE',
// Inset minimap
inset: true,
insetPosition: 'bottom-right', // 'top-left', 'top-right', 'bottom-left', 'bottom-right'
insetZoom: 1,
insetStyle: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
// Theme for story panels: 'light' or 'dark'
theme: 'dark',
// 3D terrain
use3dTerrain: false,
terrainSource: 'terrain-source-url',
terrainExaggeration: 1.5,
// Auto-advance through chapters
auto: false,
// Story metadata
title: 'Your Story Title',
subtitle: 'Your subtitle',
byline: 'By Your Name',
footer: 'Source information and credits',
// Story chapters (array)
chapters: [...]
};Each chapter in the chapters array has these properties:
| Option | Type | Description |
|---|---|---|
id (required) |
String | Unique identifier for the chapter (used as HTML id) |
alignment |
String | Position of story panel: 'left', 'right', 'center', 'full' |
hidden |
Boolean | Hide the chapter panel (map still transitions) |
title |
String | Chapter title |
image |
String | Path to chapter image |
description |
String | Chapter content (supports HTML) |
location (required) |
Object | Map view settings (see below) |
mapAnimation |
String | Animation type: 'flyTo', 'easeTo', 'jumpTo' |
rotateAnimation |
Boolean | Enable slow rotation after transition |
callback |
String | Name of a JavaScript function to call |
onChapterEnter |
Array | Layer opacity changes when entering chapter |
onChapterExit |
Array | Layer opacity changes when exiting chapter |
location: {
center: [-122.4194, 37.7749], // [longitude, latitude]
zoom: 11,
pitch: 45, // 0-85 degrees
bearing: 0, // 0-360 degrees
speed: 0.5, // flyTo speed (optional)
curve: 1 // flyTo curve (optional)
}Control layer visibility as chapters change:
onChapterEnter: [
{
layer: 'my-layer-name',
opacity: 1,
duration: 3000 // transition duration in ms
}
],
onChapterExit: [
{
layer: 'my-layer-name',
opacity: 0
}
]MapLibre works with any compatible vector tile source. Here are some free options:
- Voyager (colorful):
https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json - Positron (light):
https://basemaps.cartocdn.com/gl/positron-gl-style/style.json - Dark Matter:
https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json
- Liberty:
https://tiles.openfreemap.org/styles/liberty - Bright:
https://tiles.openfreemap.org/styles/bright - Positron:
https://tiles.openfreemap.org/styles/positron
- Streets:
https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY - Satellite:
https://api.maptiler.com/maps/satellite/style.json?key=YOUR_KEY - Terrain:
https://api.maptiler.com/maps/terrain/style.json?key=YOUR_KEY
Sign up for a free API key at maptiler.com.
The helper.html file provides an interactive tool to find map coordinates:
- Open
helper.htmlin your browser - Navigate the map to your desired view:
- Pan: Click and drag
- Zoom: Scroll or use +/- buttons
- Rotate: Right-click + drag
- Pitch: Ctrl + drag
- The location data updates automatically
- Click "Copy to Clipboard" to copy the config snippet
- Paste into your chapter's
locationobject
You can add custom GeoJSON or other data layers to your map. Add them in the map.on('load') callback in index.html:
map.on("load", function () {
// Add a GeoJSON source
map.addSource('my-data', {
type: 'geojson',
data: 'path/to/your/data.geojson'
});
// Add a layer
map.addLayer({
id: 'my-layer',
type: 'fill',
source: 'my-data',
paint: {
'fill-color': '#088',
'fill-opacity': 0 // Start hidden, control with onChapterEnter
}
});
// ... rest of the code
});To enable 3D terrain, you'll need a terrain tile source. Options include:
use3dTerrain: true,
terrainSource: 'https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=YOUR_KEY',
terrainExaggeration: 1.5,For AWS terrain tiles, you'll need to modify the terrain source configuration in index.html:
map.addSource('terrain-dem', {
'type': 'raster-dem',
'tiles': ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
'encoding': 'terrarium',
'tileSize': 256
});The storytelling map is entirely client-side and can be hosted on any static file hosting service.
This repository includes a GitHub Actions workflow for automatic deployment to GitHub Pages:
- Push your code to GitHub
- Go to your repository's Settings > Pages
- Under "Build and deployment", select GitHub Actions as the source
- The workflow will automatically deploy on every push to the
mainbranch
Your site will be available at https://<username>.github.io/<repository-name>/
- Netlify: Drag and drop the folder or connect to Git
- Vercel: Import from Git repository
- AWS S3: Upload files and enable static website hosting
- Any web server: Just upload the files
maplibre-gl-storymaps/
├── .github/
│ └── workflows/
│ └── deploy.yml # GitHub Pages deployment workflow
├── assets/ # Images and media
│ └── *.jpg # Chapter images
├── config.js # Story configuration
├── helper.html # Location finder tool
├── index.html # Main storytelling page
├── LICENSE # MIT License
└── README.md # This file
MapLibre GL JS supports all modern browsers:
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- MapLibre GL JS - Open-source map rendering library
- Scrollama - Scroll-driven interactions
This template is inspired by and based on the Mapbox Storytelling template by Mapbox. The original template was created by John Branigan and the Mapbox Solutions Architecture Team.
This version has been adapted to use the open-source MapLibre GL JS library, removing the dependency on proprietary services and API keys for basic functionality.
Special thanks to:
- The Mapbox Storytelling team for the original concept and implementation
- The MapLibre community for maintaining the open-source fork of Mapbox GL JS
- Scrollama by Russell Samora for scroll-driven interactions
MIT License
Contributions are welcome! Please feel free to submit a Pull Request.
- Ensure you're using a local development server (not opening the file directly)
- Check the browser console for errors
- Verify the map style URL is accessible
- Make sure Scrollama is loaded
- Check that chapter IDs are unique
- Ensure the page isn't inside an iframe
- Verify your terrain source URL and API key (if required)
- Check that
use3dTerrainis set totrue - Some terrain sources require specific encoding settings
