Web development: Scroll story
Last updated
Last updated
Website
Ordnance SurveyData
OS Data HubWe'll learn how to build a map-based scroll story in this tutorial - a powerful way to add location context to other content.
A scroll story with the OS Vector Tile API (click here to view animation)
This tutorial will visualise data from the OS Vector Tile API and OS Features API, using Scrollama and building on the Mapbox storytelling template.
Good storytellers bring the reader into their world. They share details to help transport people into different places and times. With maps, storytellers can add a rich visual context to deeply capture the readers imagination.
With this tutorial we will explore how to tell stories with maps. This is a pattern gaining popularity with data journalists looking to expand their storytelling toolkit. By connecting map views to sections of the story, and smoothly animating map transitions, stories can be brought to life and readers can improve their understanding of the subject - from a global to local scale.
The story will follow an October ascent of Ben Nevis, Great Britain's tallest mountain.
<iframe style="width:100%;height:400px;max-width:1200px;border:1px solid #f5f5f5;" src="/public/os-data-hub-tutorials/dist/web-development/scroll-story"></iframe>
The HTML and CSS for this tutorial is quite lightweight. In index.html
, we load the Mapbox GL JS stylsheets and JavaScript library, along with Scrollama for detecting scroll events. We also load a local style.css
stylesheet with a few styling rules.
In the document <body>
, we create two <div>
s: #map
and #story
. We then load an execute scripts: js/config.js
and js/tutorial.js
. (We also create a few helper functions and variables in js/helpers.js
) It is inside these JavaScipt files that the story content is written, the map is created, and the scroll story is set up.
config.js
In the config file, a single global variable is declared: config
. The assigned object literal has a number of properties that are accessed by the JavaScript code executed in tutorial.js
. These include the story title
, subtitle
, and a chapters
array, which will be displayed in <div>
elements on the page.
Here's an example object in this chapters
array - each property will be used in the coming code:
chapters
Just as in a book, the story is divided into chapters. Here, the story is represented as an array of chapter
objects. Each chapter
object has the following properties:
Key | Value |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(Note: this is slightly adapted from the Mapbox storytelling template, to enable multiple images in the same chapter.)
We'll be using all of these properties to create our scroll story. The location
object deserves extra attention as this is where we declare the center
, zoom
, pitch
and bearing
values - which ultimately determine what is visible on the map that appears in the viewport. Mapbox provides a handy Location Helper interface to capture these values.
tutorial.js
The logic of the app is written in tutorial.js
. We'll break up the process into a few steps. These rely on a config.js
document that has the story chapters, as well as locations and layers to load on chapter enter and exit. It's easiest to adapt that once the entire app is working, so you can see the changes you're making to the story.
We'll follow these steps:
First we will create our map, connected to the OS Vector Tile API. You'll need an API key, available on the OS Data Hub.
Then we'll create the structure of our HTML document by creating elements based on the config
data.
We'll load the story-specific vector layers onto the map. These features were fetched from the OS Features API, and are stored in local GeoJSON files.
Last we'll set up our scrollama
scroller, which detects when we scroll to a new chapter, and updates the map accordingly.
Let's dive in.
This scroll story will use some advanced animations, including ones that change the camera's pitch. Vector tiles are particularly well-suited for slick, fast animated web maps, so we'll be using the OS Vector Tile API.
We'll be using Mapbox GL JS, so let's create a map and connect to the API:
That's it! Now we've got a vector tile basemap. We'll be adding layers later on - for now let's move on to building our HTML document.
We have all the information we need for our story in the object assigned to the config
variable. We essentially create a new HTML element for each of the relevant properties we have in the config
object. This uses vanilla JavaScript. It's worth noting: this code is based on the Mapbox storytelling template, so it may look familiar.
First we'll set up the story header:
Then, we'll loop through each of the chapters and create an element with the title, images, description and so on - then append it to our features
div.
Once chapters are added and the features
div is built, we append it to our holder story
div.
And finally we add on a footer, if included. The template also includes the option to place a marker at map center for each chapter - so that gets created as well.
Now we've got a vector tile basemap and HTML with header, chapters and a footer. We're ready to add our custom map layers.
For this demo we add three GeoJSON layers to our map - a point, line and polygon. These features represent the summit marker, the trail up the mountain and the building at the top of Ben Nevis - the goal is to show what is possible. Custom layers are often crucial to the story.
Most of this logic can only happen once the map
is loaded - otherwise there is no map to add the layers to. We'll get there in a second, but first we need to create an array of objects called layers
. Each object will have information including layer id
, the source
(which is a path to the GeoJSON file), and the layer type
.
We will use JavaScript's fetch
to request the GeoJSON stored in each of these files. This will take place inside the map.on('load')
callback, which is asynchronous - important because the GeoJSON data needs to be loaded before it gets added to the map.
This results in our a geojson
key being added to each object in the layers
array, with a GeoJSON object as its value.
We then loop through the layers we want to visualise, adding each as a source, then a layer as required by the the Mapbox GL JS API. Note how we add each layer based on its type, so the right layer is added:
Great, now the GeoJSON layers are added - a point, a line and a polygon. This opens the story maps up to the capabilities of Mapbox GL JS, which can work with raster imagery, custom vector tiles, heatmaps, 3D graphics and more.
scroller
When a new chapter scrolls into view, the chapter map view needs to be triggered. Scrollama makes this easy by using IntersectionObserver to keep track of where elements are on the page.
The .setup()
method instructs which elements to watch for - in this case, <div>
s classed with .step
. The step
property defines how far from the top of the page the chapter <div>
needs to be scrolled to before the step entry event fires - we set ours to 0.5
, or 50%. And the progress
attribute tells the library 'whether to fire incremental step progress updates' - letting you tie functionality to the exact position within a chapter someone is. (We don't use this here, so we set it to false.)
Once the scroller
is set up, we just chain two more method calls: .onStepEnter()
and .onStepExit()
. These methods accept a callback function which is passed an object describing the chapter being entered or exited, and the direction of scroll.
With this functionality, plus the config
object declared in the global scope, we can manipulate our map and story chapters whenever they scroll into position.
In this case we change element classes and call the map.flyTo()
method to animate a flight to a new location - but any JavaScript code can be executed here. This opens a world of opportunity with these scroll stories using maps and other interactive visualisation libraries.
With that, we've created a scroll story of an autumn climb up Ben Nevis! We wrote our story in the config
object literal, including information about how to position the map and which layers to show. We added custom GeoJSON layers that originally came from the OS Features API, and set up an instance of scrollama
to update the map and content based on which chapter scrolled into view.