All docschevron-rightHelpchevron-rightarrow-leftTutorialschevron-rightUse Mapbox.js as a fallback

Use Mapbox.js as a fallback

Intermediate
JavaScript
Prerequisite

Familiarity with front-end development concepts.

If your web application is used by a large number of users, it is likely that some of them will be using browsers that do not support WebGL or who have intentionally disabled support for WebGL. This guide walks you through how to use Mapbox.js as a fallback when a user accesses your map with a browser that does not support WebGL.

Getting started

  • A Mapbox access token. Your Mapbox access tokens are on your Account page.
  • mapbox-gl-supported. A Mapbox GL JS plugin used to check if a browser supports WebGL.
  • Mapbox.js. Mapbox.js is a JavaScript library used to load maps via the Static Tiles API.
  • Mapbox GL JS. Mapbox GL JS is a JavaScript library used to render map data within a browser.
  • A text editor. Use the text editor of your choice for writing HTML, CSS, and JavaScript.

Add structure

For this tutorial, all code and styling will be contained within one HTML document. This tutorial begins from this basic structure:

<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='utf-8' />
  <title>Mapbox.js Fallback</title>
  <meta name='viewport' content='width=device-width, initial-scale=1' />
  <style>
    body { 
      margin:0; 
      padding:0; 
    }
    #map { 
      position: absolute; 
      top:0; 
      bottom:0; 
      width:100%; 
    }

    /* #message style rules will go here */
  </style>
</head>

<body>
  <div id='map'>
    <h1 id="message"></h1>
  </div>
  <script>
    const accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'
    /* if/else statement will go here */

    /* map initialization functions will go here */

    /* load script will go here */
  </script>
</body>

</html>

If you plan to build out a more complex implementation based on this example, you will likely wish to split this up into multiple files.

Check library support

For performance reasons, it is better to check that Mapbox GL JS is supported before going to the trouble of loading the script and initializing the map on your page. mapbox-gl-supported is a lightweight plugin library that provides a single function to check whether everything needed to run Mapbox GL JS is available on the browser used to load the script.

To import the plugin, add the following script element to the head of your document:

<script src='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-supported/v2.0.0/mapbox-gl-supported.js'></script>

Now, any subsequent JavaScript within your document will be able to use mapboxgl.supported() to return a value of either true or false. Use this method to set up an if/else statement:

if (mapboxgl.supported() === false) {
  // This is where you will load Mapbox.js & initialize a Static Tile map
} else {
  // This is where you will load Mapbox GL JS & initialize a WebGL map
}

Initialize each library

Mapbox GL JS and Mapbox.js have slightly different interfaces. As a result, you will need to write a unique function for how you would like to initialize each one. For this example, we will initialize both maps using the Mapbox Streets template style. But any style made in Mapbox Studio will work with both libraries and could be used with a fallback implementation.

Initialize a map with GL JS

The initialization function for Mapbox GL JS will take no arguments, so it can be declared after the if/else statement, like so:

function initMapboxGLJS() {}

Next, be sure to set your Mapbox access token correctly. Note that this tutorial assumes you have already declared the accessToken constant at the beginning of your document's script.

function initMapboxGLJS() {
  mapboxgl.accessToken = accessToken;
}

Finally, initialize a new Mapbox GL JS map object with the Mapbox Streets style. This tutorial uses the same basic implementation as the "Display a map" example.

function initMapboxGLJS() {
  mapboxgl.accessToken = accessToken;

  const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v12',
    center: [-123.1, 49.3],
    zoom: 10
  });
}

Initialize a map with Mapbox.js

Like Mapbox GL JS, the function you declare will first need to make sure that your Mapbox access token is properly set.

function initMapboxJS() {
  L.mapbox.accessToken = accessToken;
}

Next, initialize your map object. To ensure that you are using the Static Tiles API to retrieve static tiles for the exact same style as your Mapbox GL JS implementation, initialize your map with a styleLayer object.

function initMapboxJS() {
  L.mapbox.accessToken = accessToken;

  const map = L.mapbox.map('map').setView([49.3, -123.1], 11);

  L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v12').addTo(map);
}

Load the JavaScript library

Because you need to check browser support before loading the correct library and the goal is to have the broadest possible browser support, we cannot assume that import statements will work for all your users. So you will need to write a custom function to load a JavaScript library, create the necessary HTML elements, and inject them into the document's head.

Begin by declaring the custom function and the associated arguments. Because both Mapbox GL JS and Mapbox.js rely on complimentary CSS files, the script will require two URL strings as arguments (one for the JavaScript and one for the CSS). The function should also expect to receive one of the initialization functions we defined in the previous section, we refer to this as a "callback".

function loadScript(jsSource, cssSource, callback) {}

Then initialize all the new HTML elements you want to insert into your document. Create a script element and a style element, and load your document's head element into a constant.

function loadScript(jsSource, cssSource, callback) {
  const headElement = document.getElementsByTagName('head')[0];
  const scriptElement = document.createElement('script');
  const styleElement = document.createElement('link');
}

Now that you have defined your elements, be sure to add the CSS stylesheet to the header.

function loadScript(jsSource, cssSource, callback) {
  const headElement = document.getElementsByTagName('head')[0];
  const scriptElement = document.createElement('script');
  const styleElement = document.createElement('link');

  styleElement.href = cssSource;
  styleElement.rel = 'stylesheet';
  headElement.appendChild(styleElement);
}

To make sure that the library initialization function is called only once, write a nested function to reassign the callback variable after the first time it is used. Otherwise you risk attempting to initialize the map more than once as your page is loading.

function loadScript(jsSource, cssSource, callback) {
  const headElement = document.getElementsByTagName('head')[0];
  const scriptElement = document.createElement('script');
  const styleElement = document.createElement('link');

  styleElement.href = cssSource;
  styleElement.rel = 'stylesheet';
  headElement.appendChild(styleElement);

  function runCallback() {
    if (callback) {
      callback();
      scriptElement.onload = scriptElement.onreadystatechange = null;
      callback = null;
    }
  }
}

Finally, assign the proper JavaScript library to your <script> element and inject the element into the head of the document.

function loadScript(jsSource, cssSource, callback) {
  const headElement = document.getElementsByTagName('head')[0];
  const scriptElement = document.createElement('script');
  const styleElement = document.createElement('link');

  styleElement.href = cssSource;
  styleElement.rel = 'stylesheet';
  headElement.appendChild(styleElement);

  function runCallback() {
    if (callback) {
      callback();
      scriptElement.onload = scriptElement.onreadystatechange = null;
      callback = null;
    }
  }

  scriptElement.type = 'text/javascript';
  // Most browsers
  scriptElement.onload = runCallback;
  // Internet Explorer
  scriptElement.onreadystatechange = function () {
    if (this.readyState === 'complete') {
      runCallback();
    }
  };
  scriptElement.src = jsSource;
  headElement.appendChild(scriptElement);
}

You can now use the loadScript function within your if/then statement to load the proper library based on the results of mapboxgl.supported().

if (mapboxgl.supported() === false) {
  loadScript(
    'http://api.mapbox.com/mapbox.js/v3.2.1/mapbox.js',
    'http://api.mapbox.com/mapbox.js/v3.2.1/mapbox.css',
    initMapboxJS
  );
} else {
  loadScript(
    'https://api.mapbox.com/mapbox-gl-js/v3.2.1/mapbox-gl.js',
    'https://api.mapbox.com/mapbox-gl-js/v3.2.1/mapbox-gl.css',
    initMapboxGLJS
  );
}

Display which library is being used

For this tutorial, it will be helpful to know which library you've loaded on your page. Add some conditional text overlays to each map initialization function.

First, add the following CSS to your stylesheet to make sure the text is visible on top of the map. You can add this below the #map styling included within your HTML document's <head>.

#message {
  position: absolute;
  width: 100%;
  top: 20px;
  text-align: center;
  text-shadow: 0 0 3px rgba(255, 255, 255, 0.5);
  z-index: 1;
}

Next update your init functions to create an element that contains unique text for each function. Do this by appending the following to the end of each of your init functions:

Mapbox GL JS

function initMapboxGLJS() {
  // ...

  const messageOverlay = document.getElementById('message');
  messageOverlay.innerHTML =
    'This map was rendered in your browser by Mapbox GL JS!';
}

Mapbox.js

function initMapboxJS() {
  // ...

  const messageOverlay = document.getElementById('message');
  messageOverlay.innerHTML =
    'This map was rendered on a server & loaded by Mapbox.js!';
}

Test your implementation

Even if you are developing with a browser that has WebGL support, you can test that your implementation is working correctly by disabling the WebGL setting. This is an advanced setting and it's location will be different depending on the browser you are using and your operating system.

Final product

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Demo: Use Mapbox.js as a fallback</title>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
#message {
position: absolute;
width: 100%;
top: 20px;
text-align: center;
text-shadow: 0 0 3px rgba(255, 255, 255, 0.5);
z-index: 1;
}
</style>
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-supported/v2.0.0/mapbox-gl-supported.js"></script>
</head>
<body>
<div id="map">
<h1 id="message"></h1>
</div>
<script>
const accessToken = '<your access token here>';
if (mapboxgl.supported() === false) {
loadScript(
'http://api.mapbox.com/mapbox.js/v3.2.1/mapbox.js',
'http://api.mapbox.com/mapbox.js/v3.2.1/mapbox.css',
initMapboxJS
);
} else {
loadScript(
'https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js',
'https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css',
initMapboxGLJS
);
}
function initMapboxGLJS() {
mapboxgl.accessToken = accessToken;
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-123.1, 49.3],
zoom: 10
});
map.addControl(new mapboxgl.NavigationControl());
const messageOverlay = document.getElementById('message');
messageOverlay.innerHTML =
'This map was rendered in your browser by Mapbox GL JS!';
}
function initMapboxJS() {
L.mapbox.accessToken = accessToken;
const map = L.mapbox.map('map').setView([49.3, -123.1], 11);
L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v12').addTo(map);
const messageOverlay = document.getElementById('message');
messageOverlay.innerHTML =
'This map was rendered on a server & loaded by Mapbox.js!';
}
function loadScript(jsSource, cssSource, callback) {
const headElement = document.getElementsByTagName('head')[0];
const scriptElement = document.createElement('script');
const styleElement = document.createElement('link');
styleElement.href = cssSource;
styleElement.rel = 'stylesheet';
headElement.appendChild(styleElement);
// make sure callback isn't run more than once
function runCallback() {
if (callback) {
callback();
scriptElement.onload = scriptElement.onreadystatechange = null;
callback = null;
}
}
scriptElement.type = 'text/javascript';
// Most browsers
scriptElement.onload = runCallback;
// Internet Explorer
scriptElement.onreadystatechange = function () {
if (this.readyState === 'complete') {
runCallback();
}
};
scriptElement.src = jsSource;
headElement.appendChild(scriptElement);
}
</script>
</body>
</html>

Next steps

Now that you know your map will load even on browsers that do not support WebGL, you can start exploring more complex examples for both Mapbox GL JS and Mapbox.js.