Go to home page
Get a quotation

Backtobasics Blog


Responsive Map Markers In WordPress

Posted on: October 15th, 2014

JavaScript For Cool Maps

We recently had a client request a map for their website project implemented in WordPress. The design for the map had several critical requirements. First, the map needed to fill the window and respond to the window size. Second, the map needed to show markers for points of interest, namely cities in Myanmar, with information popups. Finally, the map needed to be artistic, not a Google map widget. The analysis led us to a JavaScript solution integrated with the WordPress backend.

We looked a number of existing plugins to accomplish this task, but none of them fully met our requirements. So, we dug into the code…

Placing the map in a div that filled the window was straightforward. Using an artistic map required the PhotoShop skills of our designer. The tricky parts were to scale and position to map so it looked good in the space, and activating the informational popups for the points of interest. Both of these required a bit of JavaScript to make them work properly. Combined with some Advanced Custom Fields, we made something that the client could then manage without the need for us to reprogram the site in the future.

First, let’s look at the final result. The following image is a screenshot of the map page in action:

saffron-map-hover

You can see that the map fills the page, and the popup for Bagan is showing, since we hovered the mouse over that city’s hot spot.

So, let’s break down the various elements going on that make this map function properly.

Let’s look at the essential HTML elements being generated by our page PHP that will display the map:

<div class="map-block-content">
      <img class="saffron-map"
            src="<?php bloginfo('template_url'); ?>/img/saffron_map.jpg" />

      <?php
            $count = 1; 
            $posts = get_posts(array(
                  'post_type' => 'cpt_maps',
                  'orderby' => 'date',
                  'order' => 'ASC',
                  'numberposts' => -1 
            )); 
      ?>

      <?php foreach($posts as $post) : ?>
      <div class="map-marker marker-<?php echo $post->ID; ?>"
            style="position: fixed; top: 0px; left: 0px;"
            data-map-loc-x="<?php echo get_field( 'x_location', $post->ID ); ?>"
            data-map-loc-y="<?php echo get_field( 'y_location', $post->ID ); ?>">

            <div class="marker-popup-container markerid-<?php echo $post->ID; ?>">
                  <div class="marker-popup">
                  <h4><?php the_title(); ?></h4>
                  <p><?php echo $post->post_content; ?></p>
                  <div class="map-info-lnk">
                        <a href="<?php echo the_field('google_map_lnk'); ?>"
                              class="google-map-lnk" target="_blank">Google Maps</a>
                        <a href="<?php echo the_field('wiki_lnk'); ?>"
                              class="wiki-lnk" target="_blank">Wikipedia</a>
                        <div class="clear"></div>
                  </div>
                  <div class="popup-arrow-down"></div>
                  </div>
            </div>
      </div>
      <?php endforeach; ?>
</div>

What are we doing here?! First, we are going to display the map as a proper img element, as opposed to a background image of the div. This is essential to allow us to scale and position the image properly within the div. Then, we walk through the custom posts (‘post_type’ => ‘cpt_maps’) that we created to allow the client to manage the map’s hot spots from the WordPress backend. For each of these posts (foreach $posts as $post), we place a div (class=”map-marker”) which will create the hot spot. You will note that we also include a class with the post’s ID, but this is just for reference, it is not important to the functionality. We also place the popup div within the marker div, but it will not be displayed until we hover over the hot spot, so it has a display: none; style. What is important to note is that the hot spot div is given an element style, since we need to update this style with JavaScript, as well as two special attributes: data-map-loc-x and data-map-loc-y. The names of these attributes is not special, we just thought it up. You will see that we set the values of these attributes from fields of the post, and these fields are created using the awesome Advanced Custom Fields plugin. We’ll talk about those values in the backend later. For now, just realize that they are the X and Y center points of the hot spot div in the dimensions of the map image’s original size.

To give you some idea of what is going on visually, here is a screen shot of the hot spot div in Chrome’s developer view:

saffron-map-developer

Next, we need to style the map’s container and image so that they properly fill the browser window. First, the div:

.map-block-content {
      width: 100%;
      height: 100%;
      position: fixed;
      top: 90px;
      left: 0;
}

The width and height styles cause the div to fill the window, obviously. Using top: 90px, we push the map down below the header, since the div is positioned fixed . I believe left: 0px pixels is obvious here, we want to be positioned at the left of the window. Width and height of 100% ensures the div fills the window.

Next, the image:

.map-img {
      position: fixed;
      top: 90px;
      left: 0px;
      z-index: 1;
}

Again, we use position: fixed; to give us control of the image location on the screen, and we push it below the header using top: 90px;. However, in this case, left: 0px; is just a placeholder – JavaScript is going to set that for us. We use a z-index here, as layers are going to come into play with the popup.

Now that we have the map on the page inside a div, we need to make it work properly, and this requires some JavaScript. First, we need to tie into the events that will allow us to control the map’s display. These are the window.load event and the window.resize events:

$j(window).load(function(){
      resizeMap();
})

$j(window).resize(function(){
      resizeMap();
});

We use the window.load function, as opposed to document.ready because this will ensure the map image has loaded.

The resizeMap() function has to perform several functions. First, it must scale and position the map appropriately for the screen, and it needs to adjust the locations of the divs we use for the hover popup function. Let’s look at the code:

function resizeMap() {
   var wWin = $j(window).width();
   var hWin = $j(window).height();
   var theImg = $j('img.saffron-map');
   if ( theImg[0] ) {
      var mapCon = $j('div.map-block-content');
      var hImg = theImg.naturalHeight();
      var wImg = theImg.naturalWidth();
      var hCon = mapCon.height();
      var wCon = mapCon.width();
      var wPct = wCon / wImg;
      var hPct = hCon / hImg;
      var pct = (hPct > wPct) ? hPct : wPct;
      var hImgF = hImg * pct;
      var wImgF = wImg * pct;
      var margin = (wCon - wImgF) / 2;
      theImg.css('position', 'fixed' );
      theImg.css('width', Math.round(wImgF) + 'px' );
      theImg.css('height', Math.round(hImgF) + 'px' );
      theImg.css('left', Math.round(margin) + 'px');
      theImg.css('top', '80px');

      $j('.map-marker').each(function() {
            var x_loc = $j(this).attr('data-map-loc-x');
            var y_loc = $j(this).attr('data-map-loc-y');
            $j(this).css( 'left', Math.round((x_loc * pct) + margin - 9) );
            $j(this).css( 'top', Math.round((y_loc * pct) + 80 - 9) );
      });
      }
}

Okay, so here the rubber meets the road. This JavaScript performs two critical functions. First, it properly positions our map image. Second, it must properly position the divs we use as the hover hot spots on the map.

First, you will see we obtain the size of the browser window. Then we get a reference to the map’s image. We check that it is not null, which would indicate that the map was not there.  Inside the null check is all of the logic. First, we get the dimensions of the image using the naturalWidth() and  naturalHeight() functions. NOTE that these functions are not universally available in every browser. For this reason, we add in Jack Moore’s very nice JQuery plugin that ensures they are there. Then we get the container for the image and get it’s dimensions. With the dimensions of the image and it’s container, we compute the scale of the image both horizontally (hPct) and vertically (hPct). We choose the greater of the two scales (into pct), as we want the image to fill the container. With this scale, determine the size we want to display the image (hImgF and wImgF) by multiplying the original image dimensions by the scale. Finally, we determine the margin of the newly sized image to properly center it horizontally in the space.

With those values computed, we use jQuery to set the position, width, height, left, and top attributes of the img element.

Now that the image is properly placed on the screen, we need to reposition the hot spot hover divs, otherwise they are not going to be in the correct locations with the rescaled and repositioned map image. To do this, we use jQuery’s each function to loop over the map-marker divs. As we loop over each div, we need to set it’s top and left attributes.

But how do we know where they go on the map?!

This is where the data-map-loc-x and data-map-loc-y attributes come in. Remember that these were set with values coming from custom fields in the WordPress backend. They are the X and Y center location of the hot spot in the map’s original dimensions. That being the case, all we need to do here is scale and reposition them based on the calculations we made to position to map image. The “– 9” that you see in that calculation is compensating for the fact that the hot spot div is 18 pixels square, and we want it centered over the hot spot’s location.

Finally, let’s take a quick look at the WordPress backend. We have defined a WordPress Page for the URL being displayed, and we use the featured image of that page to provide us the map image. We then define a custom post template for the client to define the hot spot location on the map, as well as provide the elements of the informative popup. These are the posts we are referring to in the foreach loop in our PHP code. With the custom post template defined, we use Advanced Custom Fields to define the X and Y position for the hot spot. Here is how the template looks for the client on the backend:

Saffron-map-backend

Since the client did not request that the number of hot spots be extendable, we did not move the hot spot icon into the hot spot div, but this would have been very easy to do. Just make the hot spot image the background image of the hot spot div, and retrieve it from the featured image of the post. In our case, the hot spot image is actually part of the map image. Either way, the post here uses the title for the title of the popup, the content for the content of the popup, and provides two custom fields to provide links to Google Maps and to Wikipedia in the popup. The X Location and Y Location fields are what the client uses to position the hot spot over the map.

We hope to package this up into a simple WordPress plugin in the near future. It will add the ability to put the hot spot’s tag image into the custom post, making it fully customizable.

We hope this will help others who have similar project requirements.