Tutorials and hands-on lessons for learning

Interactive Storytelling

Create guided scroll-through narratives that incorporate text, image, and video on a fully interactive map.



Some stories are best told with a map. Data journalists covering changing conditions in a population's demographics, the environment, an international conflict, or telling a simple travel story frequently provide geographic context in their graphics. We’ve designed a template to help you build a "scrollytelling" map story. No coding experience is required.

Getting started

There are a few resources you'll need to get started:

Templates:

Tools we'll use:

  • Text Editor (Atom, VSCode, JSFiddle) to write and edit your code.‍

  • Mapbox account to access: ‍‍‍

    Mapbox Studio to create your map style.

    Mapbox GL JS to add interactivity to your map and publish it on the web‍‍.

Gather content

What content is needed to tell this story? This about the narrative - what locations will be featured? What content text, links, images, or videos will be included? Before diving into the code, it might be helpful to sketch a storyboard for the visual flow of the story. Think about the different sections (or chapters) of your story when the map will change or move to a new location.

Using the template will require you to organize your story into sections (called ‘chapters’). Each chapter is associated with a particular view of a map. As you work on your story content, think about the different sections or ‘chapters’ of your story as opportunities to have the map change, add or remove a layer, or move to a new location.

Copy the template code

Download the template from the GitHub repository. Download this repository as a ZIP file using the green ‘Code’ button (if using git, clone this repository). Unzip the file, and navigate to the folder called 'src'. You will see 4 items:

  • config.js.template: This is the file you will be working in.
  • index.html: This contains the code for the template. You can open it in your browser to see your progress as you build your story.
  • An image folder for any images that you want to use.
  • helper.html: This is a utility to help you generate the location and map view for each point in the story. (Double click on it to view it in your browser.)

To get started, duplicate 'config.js.template' and rename it 'config.js' like so:

Add your access token to the configuration file

Open your configuration file, 'config.js' in the text editor of your choice. Below is the configuration file untouched. For your project, add your unique Mapbox access token on the third row.

var config = {
  style: 'mapbox://styles/mapbox/streets-v11',
  accessToken: 'YOUR_ACCESS_TOKEN',
  showMarkers: true,
  theme: 'light',
  alignment: 'left',
  title: 'The Title Text of this Story',
  subtitle: 'A descriptive and interesting subtitle to draw in the reader',
  byline: 'By a Digital Storyteller',
  footer: 'Source: source citations, etc.',
  chapters: [
    {
      id: 'slug-style-id',
      title: 'Display Title',
      image: './path/to/image/source.png',
      description: 'Description text goes here.',
      location: {
        center: [-122.418398, 37.759483],
        zoom: 13.5,
        pitch: 60,
        bearing: 0
      },
      onChapterEnter: [
        // {
        //     layer: 'layer-name',
        //     opacity: 1
        // }
      ],
      onChapterExit: [
        // {
        //     layer: 'layer-name',
        //     opacity: 0
        // }
      ]
    },
    {
      id: 'other-identifier',
      title: 'Second Title',
      image: './path/to/image/source.png',
      description: 'Copy these sections to add to your story.',
      location: {
        center: [-77.020636, 38.8869],
        zoom: 13.5,
        pitch: 60,
        bearing: -43.2
      },
      onChapterEnter: [],
      onChapterExit: []
    }
  ]
};

Note: A good practice is to create a separate token per map to be able to track traffic to different maps and manage token security. We also recommend using the URL restriction feature on the token to avoid token misuse and want to emphasize that only public tokens should be posted to public repositories. Find out more information about how to securely manage your access tokens.

Choose a basemap style

Mapbox Streets is included in the template by default. To use a different base map from Mapbox core styles or your own custom style, swap in the style URL of your choice in row 2.

We’re also going to use the default markers by leaving 'showMarkers' value to 'true'.

If you do not want to display a marker at the center of each map location, set this to false. If displaying markers, you can set the color using the markerColor property (the default color is light blue).

Choose a theme for the story text boxes. There are light and dark options. These can also be customized in the index.html file.

Choose where the story text boxes should be aligned over the map. Options are center, left, right, and full.

Set up your ‘chapter’ sections

Add as many chapters in the template as needed. Add a , between each section, but no comma at the end. Here is what a chapter looks like:

{
            id: 'slug-style-id',
            alignment: 'left',
            hidden: false,
            title: 'Display Title',
            image: './path/to/image/source.png',
            description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
            location: {
                center: [-122.418398, 37.759483],
                zoom: 8.5,
                pitch: 60,
                bearing: 0
            },
            mapAnimation: 'flyTo',
            rotateAnimation: false,
            callback: '',
            onChapterEnter: [],
            onChapterExit: []
        },

Fill out the sections as needed. Give each section a unique name in the section id property. This will become the HTML div id, so avoid spaces in the name. The title, description, and image properties are optional. The description supports HTML tags. If you are using an image, add the path to the image in the image property.

Pay attention to detail when writing and pasting in your content - the config.js file does require specific syntax and punctuation. Braces, brackets, commas, and quotes are important. If you want to include things like a paragraph break, hyperlink, or some special symbols you will need to format your text with appropriate HTML (see this guide for HTML formatting tips).

Add location information for each chapter

The location fields are needed to connect each chapter to a particular map location. You can use the ‘helper.html’ file provided with the template to identify the coordinates (map center), zoom level, pitch, and bearing, to dial in the exact map view for each chapter.

{
  location: {
      center: [-77.07567, 38.89583],
      zoom: 11.23,
      pitch: 60.00,
      bearing: 43.61
}

To do this, double click on the ‘helper.html’ file to open it in your browser, type in an address in the search bar in the top right corner, and move or zoom the map around to adjust the view until you have the desired map position. (Hint: To tilt and rotate the map, right-click and drag or press ‘Control’ while you click on the map). There is also a hosted version of this file at https://demos.mapbox.com/location-helper/

Notice the location parameters are updated in the upper left corner with everytime the map moves. When you are satisfied with the map view and location for a chapter, copy the location parameters from the helper page into your chapter's location properties section in your config.js file.



Note: Remember that there will be a block of text on the screen covering some of the map (default value is left-aligned), so you might need to adjust the map view to account for the size of your chapter textbox.

Repeat this process until you have filled in the content and locations for each of your story chapters.

Test and polish

To see how it looks, open the ‘index.html’ file in your project folder in your browser and scroll through it. You’ll probably notice some things to adjust, like the zoom level of a chapter or adjusting a chapter’s description text.

You now have a fully functional storytelling map! View an example.

Expand your story with layer styling

Take this template to the next level by styling custom layers in Mapbox Studio. By adding layers of data in Mapbox Studio you can unlock options to show more information on the map for each chapter - from points to polygons, or even custom imagery layers. Then in the next step you will use the template to control the layers’ visibility so they turn on and off at different points in your story.

You can explore this effect in the demo map 100 years of CA wildfires by Lo Benichou:



To include custom layers in your story, you will need to use a custom Mapbox style. In your custom style, you first need to upload and style your data within Mapbox Studio.

After uploading and styling data as layers, toggle the layer’s opacity (NOT the visibility) based on the layer’s desired behavior when scrolling. The opacity of a layer set in Studio will stay the same until it is changed in the config.js file (explained in the next step).





In the example above, because the 'fires' and the 'satellite' layers should be visible on the first chapter, the opacity of those layers is left unchanged. For all the layers that won’t require any adjustments, such as ‘background’ or ‘water’, you don’t need to change the opacity.

For any layer that you want turned ‘off’ by default set it to 0 opacity. Do not ‘hide’ the layer. Remember to set all the components of a layer to opacity 0, for example in a circle layer make sure the color-opacity and the stroke-opacity are both set to 0.

To display a layer for a particular chapter as the reader scrolls through the story, you will use the onChapterEnter or onChapterExit configuration options to set the desired opacity for the layer (see the next step for details). Once you’re done, you can publish your style and click on the “Share” button to get the style url.



Use your custom style url in the 'style' field at the top of your config.js file.

Update chapters with layers

Edit your chapters to have layers turn on or off in chapters. Using some additional code in each chapter section in your config.js file you can specify the opacity of individual map layers from your Studio style. These instructions will be triggered as the user scrolls through the different chapter, effectively making the map layers turn on or off.

Using the code sections under onChapterEnter and onChapterExit you can control the opacity of each layer using a number from 0 to 1. If you want a layer to turn on when the user scrolls into a chapter, set its opacity to 1 in the onChapterEnter section. If you want the layer to turn off when the user scrolls to the next chapter, set its opacity back to 0 in the onChapterExit section. Below, the code adjusts ‘california-33’ to be 0.8 on enter and 0 on exit.

onChapterEnter: [
  {
      layer: 'california-33',
      opacity: .8
  },
  {
      layer: 'california-33-line',
      opacity: 1
  },
  {
      layer: 'satellite',
      opacity: .5
  }
],
onChapterExit: [
  {
      layer: 'california-33',
      opacity: 0
  },
  {
      layer: 'california-33-line',
      opacity: 0
  }
]

Note: A chapter can enter the screen twice, once while the user scrolls down and once while the user scrolls back up. This means that if you want to be certain that layers return to their initial state despite the direction of scroll, you will want to specify the settings in both onChapterEnter and onChapterExit. For example, with the ‘fires’ layer described in the previous step, the layers ‘fires’ and ‘satellite’ are already configured in Studio to show up on the initial load (in Studio, their opacity is set to 1). Therefore you could leave onChapterEnter as is with no additional instructions needed. However, to ensure that the layers display even if the user scrolls back up to Chapter 1 from Chapter 2 then you will still want to specify the opacity for each layer in onChapterEnter. So Chapter 1 and 2 will actually look like this:

{
    id: 'chap-1',
    title: '',
    image: 'images/graph.png',
    description: '',
    location: {
      center: [-124.99958, 37.21861],
      zoom: 5,
      pitch: 0.00,
      bearing: 0.00
    },
    // we have to specify these settings for when the user comes back to this chapter
    onChapterEnter: [
      {
          layer: 'fires',
          opacity: 1
      },
      {
          layer: 'satellite',
          opacity: .5
      }
    ],
    onChapterExit: [
    ]
  },
{
    id: 'chap-2',
    title: '',
    image: '',
    description: 'From 1895 to 2018, wildfires burned 35,437,560 acres of land. California measures nearly 105 million acres. In the past 123 years, wildfires have burned the equivalent of 33.8 percent of California.',
    location: {
      center: [-124.99958, 37.21861],
      zoom: 5,
      pitch: 0.00,
      bearing: 0.00
    },
    // we have to specify these settings for when the user comes back to this chapter
    onChapterEnter: [
      {
          layer: 'fires',
          opacity: 1
      },
      {
          layer: 'satellite',
          opacity: .5
      }
    ],
    onChapterExit: [
      {
          layer: 'fires',
          opacity: 0
      },
      {
          layer: 'satellite',
          opacity: 1
      }
    ]
},

Now, when the user moves from one chapter to the next, up or down, the state of these layers will be reset.

For each chapter, you can repeat those steps, adjusting the opacity of layers as needed. The result is a story that takes the reader through relevant data points, contextualized by location and visualizations. Experience the full demo.

Publish to the web

There are many ways to publish your final story online. Check out three user-friendly, free options in our 3 Ways to Host a Web Map tutorial. Here is the demo published on Glitch.

Explore other features

In the January 2021 release of the storytelling template, there are new features that put more control in the hands of the storyteller, from specifying the layout position of individual chapters, hiding chapters to trigger a map change without obscuring the view, controlling the speed of a layer transition, or choosing the color of the marker.

Other new features enhance the impact of the story, like adding 3D terrain, starting a slow map rotation animation for a particular chapter view, or calling a custom JavaScript function when entering a chapter. Use the custom function to include a data-driven graph alongside the chapter text, or to control other elements on the page, like toggling map legend visibility. For low-bandwidth regions or between distant map locations, skip a long map transition and instead jump to the next view.

Recap of configuration options

Note: items in bold are required.

style: This is the Mapbox style url to use for the app. It can be a standard style, or a custom style from your Mapbox account. Use a custom style if you want to include custom data or layers.

accessToken: Your Mapbox access token.

showMarkers: This controls whether markers are shown at the centerpoint of each chapter. If true, the map will display a default blue, inverted-teardrop icon.

markerColor: Accepts hexadecimal, RGB, and color names compatible with CSS standards. If showMarkers is true, this property will override the default light blue marker color.

theme: Two basic themes (light and dark) are available.

use3dTerrain: Enables 3D terrain. (Optional)

title: The title of the overall story. (Optional)

subtitle: A subtitle for the story. (Optional)

byline: Credit the author of the story. (Optional)

footer: Citations, credits, etc. that will be displayed at the bottom of the story.

chapters: This contains all of the story content and map controls for each section of the story. Array of objects

  • id: A slug-style ID for the chapter. This is read by the JavaScript driving the app and is assigned as an HTML id for the div element containing the rest of the story. A best-practice format would be to use kebab case, like my-story-chapter-1.
  • alignment: This defines where the story text should appear over the map. Options are center, left, right, and full. When the browser window is less than 750 pixels wide, the story will be center aligned.
  • hidden: Sets the visibility of the chapter to hidden when true. The chapter will still trigger a map and layer transition.
  • title: The title of the section, displayed in an h3 element.
  • image: The path to an image to display in this section.
  • description: The main story content for the section. This should be aligned with what the reader is seeing on the map. In the vanilla version, this field will render as HTML. Images, links, and other items can be included as HTML.
  • location: Details about the map display and camera view.

    • center: Center coordinates of the map, as longitude, latitude
    • zoom: Zoom level of the map.
    • pitch: Angle of the map view. 0 is straight down, and 60 is highly tilted.
    • bearing: Degrees of rotation clockwise from North (0). Negative values represent counter-clockwise rotation.
  • mapAnimation: Defines the animation type for transitioning between locations. This property supports 'flyTo', 'easeTo', and 'jumpTo' animations. If not specified, defaults to flyTo.
  • rotateAnimation: Starts a slow rotation animation at the end of the map transition when set to true. The map will rotate 90 degrees over 24 seconds.
  • callback: Accepts the name of a JavaScript function and executes the function. Use this if you have custom code you want to run for a chapter, like turning a legend on or off, adding data from an API request, or displaying an interactive graph.
  • onChapterEnter: Layers to be displayed/hidden/muted when the section becomes active. Array of objects

    • layer: Layer name as assigned in Mapbox Studio.
    • opacity: The opacity to display the layer. 0 is fully transparent, 1 is fully opaque.
    • duration: The length of the opacity transition, numeric, in milliseconds. Default is 300. This is an optional parameter and can be omitted.
  • onChapterExit: Same as onChapterEnter except it is triggered when the section becomes inactive. Array of objects

This release is also backwards compatible, so it’s easy to upgrade an existing story configuration file: drop in the new index.html file and the story will still work, then add new configuration options like enable3dTerrain.

The template does not rely on any particular CSS framework, fonts, or images. There are some basic styles in the head of the HTML file that can be changed, so feel free to adapt and add to these to match the site and story brand.

Finished project

View our finished example map at https://demos.mapbox.com/scrollytelling/.

Need more help? Ask questions on Stack Overflow or contact Mapbox Support. If you are working on a social good project, contact the Mapbox Community Team.

Want to share what you’ve built? Tweet it with #builtwithmapbox

Was this page helpful?