Improve perceived performance of Mapbox GL JS using the Static Images API

intermediate
JavaScript
Prerequisite

Familiarity with front-end development concepts including JavaScript and API calls.

Many things can impact the performance of a Mapbox GL JS map, such as network hardware constraints, complex application requirements requiring large JavaScript payloads, or highly dense and detailed Mapbox styles. Using the Mapbox Static Images API with your Mapbox GL JS maps won't solve these performance issues, but it will allow you to improve the perceived performance for your users since you will be able to immediately show a static image while Mapbox GL JS works in the background. Showing this static image requires one HTTP API call.

In this tutorial, you will be building a small website landing page. This page will first load a static image of your map, wait for the interactive map to load in the background, then swap to this vector map once rendering is complete.

Getting started

There are a few resources you’ll need before getting started:

  • An access token from your Mapbox account. You will use an access token to associate a map with your account. Your access token is on the Account page.
  • A Mapbox style ID from your Mapbox account. You will use this style in both the Static Images API request and the vector map.
  • A text editor. Use the text editor of your choice for writing HTML, CSS, and JavaScript.

In this tutorial, you will create an interface using HTML and a map using Mapbox GL JS. At the end of this tutorial, you will have a functioning landing page that requests a static image and loads a Mapbox GL JS map in the background. This technique gives the user instant visual feedback and provides a visual buffer to allow the dynamic content to load.

Structure the webpage

Start by pasting the following code into your text editor. This code will create a scaffold for adding your map and website content.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Display a map</title>
    <script src=https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js></script>
    <link
      href=https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css
      rel="stylesheet"
    />
    <style>
      body {
        margin: 5px 20px 20px 20px;
        padding: 2em;
      }
      #content {
        width: 600px;
      }
    </style>
  </head>
  <body>
    <header>
        <h2>Title of my webpage</h2>
    </header>
    <div id="content">

    </div>
  </body>
</html>

Save this file and open it in your browser. You will only see a header at the top.

Build the map container

The next step is to specify where the map will go. Copy the following into the body tag of your page, inside the content div.

  <div id="container">
      <div id="map"></div>
  </div>
  <ul>
      <li>Item 1</li>
      <li>Item 2</li>
      <li>Item 3</li>
  </ul>

This code creates three items. First, it creates a parent container for the map and the static image (container). Second, it creates a div for the Mapbox GL JS map (map). Third, it creates a list in which to host some basic content.

Save your work and reload the browser page.

You should only see the header and the list content.

Add the Mapbox GL JS map

The next step is to create a Mapbox GL JS map. This map is going to be attached to the map div you created in the last step. Its size will be 600 pixels by 400 pixels.

Underneath the closing <div> tag in your HTML body, add a <script> node with the following.

<script>
    mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
    const map = new mapboxgl.Map({
        container: "map", // container id
        style: "mapbox://styles/mapbox/streets-v11", // stylesheet location
        center: [-85.757, 38.25], // starting position [lng, lat]
        zoom: 10, // starting zoom
    });
</script>

Add CSS

The map is technically loading now, but it isn't shown on the page. Adding some styling using CSS will tell the page how big to draw the map.

Copy the CSS below and paste it into the <style> tag of the page.

#container {
    width: 600px;
    height: 400px;
    margin: 20px 0px;
    display: grid;
}
#map {
    grid-column: 1;
    grid-row: 1;
    width: 100%;
    height: 100%;
}

Test the map's performance

Now that the map is loading, you may notice that it takes a second to fetch all the data and render the map.

This may feel like a performant application. But performance can vary depending on hardware, network conditions, and map complexity, which means that the map may not load quickly under all circumstances.

Add a static image of your map

Integrating the Static Images API requires two individual steps. First, create a <div> where the image will live and update the CSS. Second, wait for the map to load and then switch the static image off, revealing the Mapbox GL JS powered map below.

Update the HTML and CSS

In the <container> div, add a single <div> to house the new static image.

<div id="container">
    <div id="map"></div>
    <div id="static"></div>
</div>

Save and reload the page. You may notice that the original map has now been compressed. Resolve this by updating the CSS in the <style> tag with the following rule.

#static {
    width: 100%;
    height: 100%;
    grid-column: 1;
    grid-row: 1;
}

The map will return to its previous state.

Align image to map

Now that the building blocks of the app are all in place, the final step is to fetch the static image, overlay it on the <map> div, and wait for the Mapbox GL JS map to stop loading.

The URL format is described in the Static Images API documentation, with the following convention:

https://api.mapbox.com/styles/v1/{username}/{style_id}/static/{lng},{lat},{zoom}/{width}x{height}?{access_token}

The first point of alignment is coordinates. It is important that the URL request and the Mapbox GL JS map share the same size, centerpoint, and zoom. In this example the map is instantiated at:

center: [-85.757, 38.25], // starting position [lng, lat]
zoom: 10, // starting zoom

This translates to https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/-85.757,38.25,10/ in the URL above.

The second point of alignment is sizing. The dimensions of the image should be the same as the container div, which is 600px by 400px. This creates the final URL:

https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/-85.757,38.25,10/600x400?access_token=YOUR_MAPBOX_ACCESS_TOKEN

Fetch the image

To fetch the image, change the CSS rule for #static to include the Static Images API URL in a background-image property.

#static {
    background-image: url("https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/-85.757,38.25,10/600x400?access_token=YOUR_MAPBOX_ACCESS_TOKEN");
    width: 100%;
    height: 100%;
    grid-column: 1;
    grid-row: 1;
}

If you look closely, you will see the Mapbox GL JS map and the image load, except the map will be sitting on top of the image. This is the opposite of what is needed. To fix this, edit the CSS of the #map.

#map {
    grid-column: 1;
    grid-row: 1;
    width: 100%;
    height: 100%;
    visibility: hidden;
}

This will turn off the map div, leaving the static image in its place. You will notice the page loads instantly, but now the image stays put and no vector map loads.

Swap when the map loads

Setting visibility: hidden does not prevent the map from loading. It only prevents users from seeing the map load. You can use this behavior and Mapbox GL JS event listeners to toggle the static image off when the map has loaded completely.

The event to listen for is map.on('load). To take advantage of this event, add the following to your <script> tag.

map.on("load", function (e) {
    const mapContainerEl = document.getElementById("map");
    mapContainerEl.style.visibility = "visible";
});

This creates an event listener that waits until Mapbox GL JS fires the load event, which only happens once, then selects the map div and toggles the visibility setting.

Final product

You have now completed the example from the beginning of the tutorial.

The final HTML file will look like the following:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Display a map</title>
    <meta name="robots" content="noindex, nofollow" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <script src=https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js></script>
    <link
      href=https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css
      rel="stylesheet"
    />
    <style>
      body {
        margin: 5px 20px 20px 20px;
        padding: 2em;
      }
      #content {
        width: 600px;
      }
      #container {
        width: 600px;
        height: 400px;
        margin: 20px 0px;
        display: grid;
      }
      #map {
        grid-column: 1;
        grid-row: 1;
        width: 100%;
        height: 100%;
        visibility: hidden;
      }
      #static {
        background-image: url("https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/-85.757,38.25,10/600x400?access_token=YOUR_MAPBOX_ACCESS_TOKEN");
        width: 100%;
        height: 100%;
        grid-column: 1;
        grid-row: 1;
      }
    </style>
  </head>
  <body>
    <header>
      <h2>Title of my webpage</h2>
    </header>
    <div id="content">
      <div id="container">
        <div id="map"></div>
        <div id="static"></div>
      </div>
      <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
      </ul>
    </div>
    <script>
      mapboxgl.accessToken = "YOUR_MAPBOX_ACCESS_TOKEN";
      const map = new mapboxgl.Map({
          container: "map", // container id
          style: "mapbox://styles/mapbox/streets-v11", // stylesheet location
          center: [-85.757, 38.25], // starting position [lng, lat]
          zoom: 10, // starting zoom
      });

      map.on("load", function(e) {
        var mapContainerEl = document.getElementById("map");
        mapContainerEl.style.visibility = "visible";
      });
    </script>
  </body>
</html>

For more information about Mapbox Static Images API, read the product documentation. For more information about Mapbox GL JS, read the product documentation and try out the examples.

Was this page helpful?