« Back

Unobtrusive Google Maps with jQuery

Published Jun 27 2009.

Google Maps is a great tool for displaying maps on webpages. Using them on your page doesn’t come without a price, though. Typically, you want a webpage to have finished loading in about 200-300 milliseconds. Loading a Google map can take up to a second, effectively tripling the load time of your page.

This article is about keeping your page speedy when you use Google Maps, avoiding the long page load times you’ll get with vanilla Google Maps integrations.

Here’s the final result we’re aiming for. A static map that, when clicked, loads a interactive map. Why this is a good thing is explained in the article below.

Click the map!

Open in a new window

Update: the plugin

I created a pluginized version of this code. It’s available here, on Github. Because installing a plugin is much better than copy/pasting text from a web page. Read the article, though, so that you grok why you should use this plugin in the first place.

Using the Static Google Maps API

Since the main goal of showing a map is to show a map, all you really need to do that is a flat JPEG. That is exactly what the Static Google Maps API provides: A flat JPEG version of their interactive maps.

<img src="http://maps.google.com/staticmap?center=40.714728,-73.998672&amp;zoom=13\
&amp;size=500x300&amp;maptype=mobile&amp;markers=40.714728,-73.998672\
&amp;sensor=false&amp;key=MAPS_API_KEY" />

One single image loads way faster than the javascript and image heavy interactive Google maps.

Obviously, you can’t pan and zoom this flat JPEG. So what do we do in order to get the best from both worlds; fast load times and interactive maps?

Loading interactive maps on demand

The first thing a user does with a map is to look at it. At this time in the interaction, you don’t need a pannable and zoomable map. The abiliy to pan and zoom is not required until the user actually clicks on the map, attempting to perform these operations.

Loading the interactive map on demand means loading it at this time; when the user clicks the static map. When this happens, it is replaced with a interactive map.

This interaction flow is a big win. Your page will load faster, because displaying images is faster than loading interactive Google maps. Clicking the map isn’t something users want or can do until the page has loaded completely anyway, and some users might not want to browse an interactive map at all. Holding back the interactive maps untill they are needed makes sense in a lot of cases.

Implementing

We need to hook the click event of the image. In this event, we need to load the Google Maps javascripts, and replace the image with a div that contains the interactive map.

Here’s a basic skeleton:

// Only a skeleton!
  
window.googleMapsLibraryLoaded = function(){
  var target = $(document.createElement("div")).css({width: 500, height: 250});
  $("#map").replaceWith(target);

  var point = new google.maps.LatLng(40.714728, -73.998672);
  var map = new google.maps.Map(target[0], {
    zoom: 13, center: point,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  });
  new google.maps.Marker({position: point, map: map});
}

$("img#map").click(function(){
  var url = [
    "http://maps.google.com/maps/api/js?",
    "sensor=false",
    "&callback=googleMapsLibraryLoaded"
  ]

  $.getScript(url.join(""));
});

In the click event of the image, $.getScript loads the external Google Maps scripts .The callback parameter contains the name of our function, googleMapsLibraryLoaded. When the Google Maps scripts has finished initializing, it will call this callback function. In this callback, we replace the image with a new div that contains the interactive Google map.

Using the data from the image to load the map

In the basic implementation above, the longitude, latitude, map size and zoom level is duplicated in the scripts. A sensible implementation would use the data already provided by the static image. The URL to the image (the src attribute) contains all these parameters, and since we are good unobtrusive javascript citizens, we will use these parameters in our javascripts instead of passing it on via <script></script> tags.

The full URL is just a string, so we will have to parse it. This adds some overhead, but not too much to be a real problem. We will also use this opportunity to move all the code into an object which we will name UnobtrusiveGoogleMaps.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>Unobtrusive Google Maps</title>
</head>
<body>
  
  <img src="http://maps.google.com/staticmap?center=40.714728,-73.998672&amp;zoom=13&amp;size=500x300&amp;maptype=mobile&amp;markers=40.714728,-73.998672&amp;sensor=false&amp;key=ABQIAAAAXYawz0HUhQEfK4yV1l2SWxQqCyHoT6LEDbPCa7oX2Mha8N4j1RSNHHsyBLVjnkvsR4rLjrpwwQWLKA" id="map" />
  
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"
    type="text/javascript"></script>
  <script>
    window.UnobtrusiveGoogleMaps = {
      initialize: function(selector){
        this.targetDomObject = $(selector);
        this.targetDomObject.click(function(){
          UnobtrusiveGoogleMaps.loadInteractiveMap();
        });
        
        this.options = this.getOptionsFromQueryParameters();
      },
      
      getOptionsFromQueryParameters: function(){
        var src = this.targetDomObject.attr("src");
        var options = {}
        var queryParameters = {};
        $.each(/\?(.+)/.exec(src)[1].split("&"), function(i, x) {
          var r = x.split("="); queryParameters[r[0]] = r[1];
        });
        
        options.zoom = parseInt(queryParameters["zoom"]);

        var sizes = queryParameters["size"].split("x");
        options.width = parseInt(sizes[0]);
        options.height = parseInt(sizes[1]);

        var latLong = queryParameters["center"].split(",");
        options.latitude = parseFloat(latLong[0]);
        options.longitude = parseFloat(latLong[1]);
        
        return options;
      },
      
      loadInteractiveMap: function(){
        var url = [
          "http://maps.google.com/maps/api/js?",
          "sensor=false",
          "&callback=UnobtrusiveGoogleMaps.googleMapsLibraryLoaded"
        ];

        $.getScript(url.join(""));
      },
      
      googleMapsLibraryLoaded: function(){
        var target = $(document.createElement("div"));
        target.css({width: this.options.width, height: this.options.height});
        this.targetDomObject.replaceWith(target);
        
        var point = new google.maps.LatLng(this.options.latitude, this.options.longitude);
        var map = new google.maps.Map(target[0], {
          zoom: this.options.zoom, center: point,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        });
        new google.maps.Marker({position: point, map: map });
      }
    }
  
    $(function(){
      UnobtrusiveGoogleMaps.initialize("#map");
    });
  </script>
</body>
</html>

window.UnobtrusiveGoogleMaps makes it a global variable. UnobtrusiveGoogleMaps alone would also be a global variable, but I prefer to be explicit about it, so that it is obvious that it is global, and I didn’t just forget to add a var in front of it.

googleMapsLibraryLoaded gets it’s data from this.options instead of being hardcoded. this.options contains the values parsed out of the static map image’s URL.

That is all there is to it!

Check out the working finished example!


Got a comment? Send me an e-mail! Or use Twitter.