Map Tips: Speeding up page load times with the Google Maps JavaScript API
Monday, September 21, 2015
Editor’s note: ‘Map Tips’ are designed to answer frequently asked questions about Google Maps APIs. Since web performance is so hot right now, we want to provide some useful information. For important tips on how to deploy Google’s mapping tools, read more from Mark McDonald, Google Developer Relations. Also, our friends over at Wikipedia have just done something similar!
Asynchronously loading JavaScript on your pages can give you huge performance wins. We have just updated all of our JavaScript Maps API samples and you can make these changes to your site, too. Read on to find out what we did and how it works, or skip straight to Unblocking scripts to see what you can start to update on your site today.
Loading JavaScript 101
There are plenty of great developer resources that describe browser rendering processes in detail, and you should read them all, but right now we’re interested in how <script> tags affect page load time. Here’s a quick refresher.
- As a browser loads its stream of HTML content representing the page to render, it builds DOM and CSSOM trees representing the page structure and style, respectively.
- JavaScript can change both the DOM and the CSSOM, for example through document.createElement() and myElement.style.backgroundColor.
- Ergo, when the browser hits a <script> tag, either in-line or externally hosted, it has to pause any further processing of the script and any further HTML rendering until the script has been fetched and any CSS declared prior has been fetched and processed. Take note—this is critical as it affects the speed at which the first round of content is displayed in the browser.
Ilya Grigorik has written on this topic in detail, so check out his article on asynchronously loading JavaScript to find out more.
Unblocking scripts
The first change we made to our samples was to add the async and defer attributes to the script tag:
<script src="https://maps.googleapis.com/maps/api/js" async defer></script>
The async attribute instructs the browser to fetch the script asynchronously, then execute the script as soon as it is retrieved, removing both the block on DOM construction and the wait for CSSOM construction.
The defer attribute is an older, more supported attribute that instructs the browser to execute the script when the DOM is ready. We’ve included it because the async attribute is not supported in all browsers (specifically, older versions of Internet Explorer). In older browsers (as far back as Internet Explorer 5.5), defer still speeds up the page load, so it’s worth including even though it’s ignored in newer browsers when async is present.
The WHATWG spec explains the behavior:
There are three possible modes that can be selected using these attributes. If the async attribute is present, then the script will be executed as soon as it is available, but without blocking further parsing of the page. If the async attribute is not present but the defer attribute is present, then the script is executed when the page has finished parsing. If neither attribute is present, then the script is fetched and executed immediately, before the user agent continues parsing the page.
The second change we made to our samples was to move the map initialization code from the window’s onLoad event to use a callback. So this code:
<script src="https://maps.googleapis.com/maps/api/js"></script>
...
<script>
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: -34.397, lng: 150.644},
zoom: 8
});
}
google.maps.event.addDomListener(window, 'load', initMap);
</script>
Now looks like:
<script>
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: -34.397, lng: 150.644},
zoom: 8
});
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?callback=initMap"
async defer></script>
This change allows the Maps API code (which now executes as soon as it has been fetched) to run the map initialization code as soon as it can. We’ve also moved the <script> tag after the custom code, to avoid a race condition where the Maps API code could load before initMap is declared.
When implementing this, you should also ensure that any DOM objects you need (particularly the map) are going to be available when your code is called, which could be before DOMContentLoaded fires. For simple inline scripts, you can guarantee this by putting the script after the element in the page.
The numbers
Here are the before & after screenshots of the waterfall from Chrome DevTools. The first image shows the old technique, without async/defer or any callbacks. In addition to the code above, the test code logs window.performance.now() at the end of initMap, which is used to calculate the point at which you can start making map customizations.
Before:
DOMContentLoaded is triggered at ~600ms (658ms in the screenshot below) and map customizations can begin around 700 - 1000ms.
After:
DOMContentLoaded is triggered at 35ms and map customizations can begin at around 300 - 500ms.
I can use this everywhere, right?
Alas, no. As the script tags are no longer synchronously loading in a predictable order, any code using the google.maps namespace needs to run within the callback or sometime afterwards.
To work around this constraint, you can either fall back to loading the API synchronously or mix in your code with the parent class once it’s available. If you choose to load it synchronously, you can still move the <script> tag to the end of the <body> section so your page renders quickly.
There are 2 particular cases you should look out for:
- Subclassing anything in the Google Maps JavaScript API (for example, custom overlays). You must ensure your classes are defined after the callback has been executed.
- LatLngs defined outside of the map initialization function. Most functions now accept LatLngLiterals, so you can simply pass { lat: 33, lng: 151 }.
Updating your own code
This optimization applies beyond just the Maps APIs and you should check your own sites too. After you’ve updated your Maps APIs code, look for externally loaded scripts and determine if your code needs to be executed at the point at which it has been defined. If not, add a callback mechanism and the async/defer attributes.
While you’re there, many popular JavaScript libraries support (in fact, encourage) use of the async attribute. You can find out which ones using the PageSpeed tool.
We hope you can squeeze even a few extra milliseconds of performance out of your page loads. Remember that performance is a feature!