Embellishing Your Google Map with CSS3 and jQuery

Interactive maps are neat. It’s not hard to get a basic map embedded on your page, but Google provides a rich and easy to use API for embedding and decorating maps on your web page, so there should be no excuse for boring default maps.

In this article I’ll share a technique I used recently to spice up the “Places to shop and eat” map on Vegan Melbourne, using a combination of the Google Maps API, jQuery animation, and some newer CSS effects.

vegan-melbourne

There are two options for creating a Google map overlay: KML, or custom javascript. KML is an XML file format used by Google Earth and maps that can specify placemarks and other overlays. It is great for standardization and for efficiently displaying large amounts of data in an overlay, but it limits your metadata and customization options.

For the other option, Google provides an API to add placemarkers and other features in real time to a map. This is what we will be using in this article. Note that we are using version 3 of the API—many tutorials you will find on the internet are using an older version which has been deprecated.

The Basics

To get started, we need to load up jQuery and the Google Maps API library. Note that when including Google Maps you have to set the sensor parameter to true if you are detecting the user’s location (say via GPS). For the typical browser scenario though false is fine.

<!DOCTYPE html>
<html>
<head>
  <script
    src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js">

  </script>

  <script
    type="text/javascript"
    src="http://maps.google.com/maps/api/js?sensor=false">
  </script>

  <script>
    // New code goes here
  </script>

</head>
<body>
  <div id='map_canvas' style='height:500px; width: 700px'></div>
</body>
</html>

That doesn’t show a map yet, it just gets everything ready with the right libraries loaded and a div to place the map into. The rest of the article will assume this basic template.

Every page with a map on it will need to start by actually creating a map object. This is done by specifying some basic options, such as the initial location and zoom level, and the container to place the map into.

$(function() { // jQuery onload handler
  var melbourne = new google.maps.LatLng(-37.813611, 144.963056);
  var mapOptions = {
    zoom:      12,
    center:    melbourne,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }

  var map = new google.maps.Map($("#map_canvas")[0], mapOptions);
});

From here, we can place various items on to the map. The most common is the “Placemark”, which you have no doubt seen on countless maps, but there are other options such as circles and images. Let’s put a marker on top of Flinders St Station, the hub of Melbourne’s train network.

// continuing on from above
var map = new google.maps.Map($("#map_canvas")[0], mapOptions);
var marker = new google.maps.Marker({
  position: new google.maps.LatLng(-37.818078, 144.966811),
  map:      map,
  title:    'Flinders St Station'
});

There are plenty of options for making your markers a little more snazzy. Let’s give this one a nicer icon and some gratuitous animation.

var marker = new google.maps.Marker({
  position: new google.maps.LatLng(-37.818078, 144.966811),
  map:      map,
  title:    'Flinders St Station',
  icon:     'http://google-maps-icons.googlecode.com/files/train.png'
});
marker.setAnimation(google.maps.Animation.BOUNCE); // Also try DROP

That bounce is quite annoying. Please do remove it again before continuing. I believe I have made my point: you can customize your placemarks.

Behaviour

The Google Maps API exposes a large number of events that we can react to in our program. They are documented against each type of object you can place on a map. For this example we will stick to the plain vanilla click event, but there is plenty of scope for interesting interactions with other mouse events and drag/drop.

google.maps.event.addListener(marker, 'click', function() {
  alert("clicked Flinders St");
});

This is the hook we need to create interesting interactions that go above and beyond the standard “info window” pop-up that is the standard dialog used on Google Maps. Using the manipulation and animation functions provided with jQuery, it is trivial to put together something shiny. We will use a semi-transparent overlay that slides into the map to provide extra detail about the clicked placemark. This requires some new HTML:

<div class='map'>
  <div id='map_canvas' style='height:500px; width: 700px'></div>
  <div id='placeDetails'>
    <h1>Flinders St Station</h1>

    <p>
      This is a pretty major train station.
    </p>
  </div>
</div>

It also requires some styling for the new “info window”. There are quite a few lines of CSS in the following snippet, but don’t panic it should be pretty simple to follow. The important parts are labeled with comments.

<style>
  .map {
    width: 700px;

    /* The following are required to allow absolute positioning of the
     * info window at the bottom right of the map, and for it to be hidden
     * when it is "off map"
     */
    position: relative;
    overflow: hidden;
  }
  #placeDetails {
    /* Place the div off the bottom right of the map */
    position: absolute;
    width: 300px;
    bottom: 0;
    right: -320px;
    padding-left: 10px;
    padding-right: 10px;

    /* Semi-transparent background */
    background-color: rgba(0,0,0,0.8);
    color: white;
    font-size: 80%;

    /* Rounded top left corner */
    border-top-left-radius: 15px;
    -moz-border-radius-topleft: 15px;
    -webkit-border-top-left-radius: 15px;
  }

  /* Fit the text nicely inside the box */
  h1 {
    font-family: sans-serif;
    margin-bottom: 0;
  }
  #placeDetails p {
    margin-top: 0;
  }
</style>

Now we have a structure that we can wire up using jQuery. The first step is to make the window simply slide in and out in response to the click event on our placemark.

var currentPlace = null;
var info = $('#placeDetails');

google.maps.event.addListener(marker, 'click', function() {
  if (currentPlace) {
    info.animate({right: '-320px'});
    currentPlace = null;
  } else {
    info.animate({right: '0'});
    currentPlace = marker;
  }
});

This is a great start, but we need to abstract it out somewhat if it is to scale to more than one placemark.

JSON to the rescue

Let’s add Southern Cross Station to the map, the other major rail hub in Melbourne. To do this we will separate our data from our code by moving the definition of a placemark out into a hash. This is always a good idea that leads to understandable and maintainable code.

var places = [
  {
    "title": "Flinders St Station",
    "description": "This is a pretty major train station.",
    "position": [ -37.818078, 144.966811 ]
  },
  {
    "title": "Southern Cross Station",
    "description": "Did you know it used to be called Spencer St Station?",
    "position": [ -37.818358, 144.952417 ]
  }
]

var currentPlace = null;
var info = $('#placeDetails');

$(places).each(function() {
  var place = this;
  var marker = new google.maps.Marker({
    position: new google.maps.LatLng(place.position[0], place.position[1]),
    map:      map,
    title:    place.title,
    icon:     'http://google-maps-icons.googlecode.com/files/train.png'
  });

  google.maps.event.addListener(marker, 'click', function() {
    $('h1', info).text(place.title);
    $('p',  info).text(place.description);

    if (currentPlace == marker) {
      info.animate({right: '-320px'});
      currentPlace = null;
    } else {
      info.animate({right: '0'});
      currentPlace = marker;
    }
  });
});

This is a neat extraction—it is now easy to see and add new places, as well as adapt to any extra meta-data that may be available (such as a phone number for the place). Being able to deal with arbitrary extra meta-data is a strength of this approach which is not easy to do using KML. Note how easy it is to change the title and description of the info window using the jQuery text function.

The other benefit is that the underlying data for the map can now be served up from elsewhere, such as a server process generating JSON on the fly. Move the places array out into a new file places.json (without the var places = part), and we can fetch it easily with jQuery.

$.getJSON('places.json', function(places) {
  $(places).each(function() {
    // As above
  });
});

You may have trouble running this part if you are testing directly off the filesystem due to ajax security policies. The easiest way around it is to serve up both files via a webserver, which is an easy one line if you have ruby installed, otherwise putting it under your apache www root may be comfortable for you.

ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port => 3000, :DocumentRoot => Dir.pwd).start'

Finishing touches

You now have the framework needed to go forth and make your own pretty maps. For bonus points, we can clean up the user experience a bit. When you click one station, then click another, it is currently a bit … bland. There is also no indication on the map as to which marker is selected. Let’s slide the window out again before switching the data, and change over the icon of the currently selected item.

var icons = {
  'train':          'http://google-maps-icons.googlecode.com/files/train.png',
  'train-selected': 'http://dl.dropbox.com/u/3120508/train-selected.png'
}
google.maps.event.addListener(marker, 'click', function() {
  var hidingMarker = currentPlace;
  var slideIn = function(marker) {
    $('h1', info).text(place.title);
    $('p',  info).text(place.description);

    info.animate({right: '0'});
  }

  marker.setIcon(icons['train-selected']);

  if (currentPlace) {
    currentPlace.setIcon(icons['train']);

    info.animate(
      { right: '-320px' },
      { complete: function() {
        if (hidingMarker != marker) {
          slideIn(marker);
        } else {
          currentPlace = null;
        }
      }}
    );
  } else {
    slideIn(marker);
  }
  currentPlace = marker;
});

Much nicer. That looks totally ace.

See the demonstration page. View source for the full code.

Protips

  • Google Map Icons is a gold mine of icons you can use and adapt for your map. Do donate if you use them!
  • If you really want to serve the JSON data across domains, look into JSONP.
  • If you install the json gem under ruby, you get access to the prettify_json.rb script, which you can pipe json into to make it easy to read.
  • That one line ruby server is pretty much unkillable. My roundabout way is CTRL-Z to background it, then ps | grep "ruby -rwebrick" | cut -d ' ' -f 1 | xargs kill -9. Feel free to suggest a better way in the comments!
  • The Google Maps API documentation is really quite spectacular. Get comfortable with it.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Paul Carney

    Awesome write-up, Xavier! I had been looking for a reason to incorporate Google Maps into my jQuery playing and your post here is a great example of neat things you can do while learning – Thanks!

  • Mark Bain

    This is great. Going to delve into the API right away. Cheers.

  • Bill D

    Very nice, thanks for the info! Only additional thing I’d kinda like to add to that would be something like the info box ‘retreating’ after 10 seconds or so, because it’s slightly frustrating when it overlays that part of the map that you’re wanting to ‘explore’, so currently, to make it go away after you’ve read the info, and the icon you’d clicked on is under the overlay, you have to click on some other icon a couple of times to get rid of it, which isn’t necessarily an intuitive procedure and kinda throws you off your search.

    • http://xaviershay.com/ Xavier Shay

      I don’t like auto-hiding, especially if I’m trying to dial the phone number. Maybe a “close” button in the slideout window?

  • Stormrider

    No demos?

  • Skweekah

    The overlay background is broken in IE8 (and possibly other versions) of the http://veganmelbourne.com.au/ map.

    • http://xaviershay.com/ Xavier Shay

      Ah yes, IE doesn’t support rgba for the background color. I have added a conditional style to just make it black for IE. I’d be surprised if there wasn’t a fancy way to get transparency working though – background PNG perhaps?

  • w2ttsy

    Campaign Monitor’s World View (http://www.campaignmonitor.com/worldview/demo/) is a spectacular example of map animation at work, although they chose CSS3 animations instead of jQuery.

  • Arunan

    Thanks for this one Xav, you read my mind… :)

  • Katja Hollaar

    Thanks for this post! Makes things easier than trying to figure out some complex plugins.

    One question. How can you position your infobox relative to the marker position (somewhere under)? I guess I can catch mouse click position, but can’t make it work with google click event.

    Thanks in advance!