Irregular CSS Sprites

Ireland Northern Ireland Scotland England Wales

Britain and Ireland

CSS sprites are a great technique for fast loading rollover effects for images, such as the navigation on this site. However, what if the area we want rollovers for overlapping, irregular areas? This is more difficult to do with the standard CSS sprites techniques, since these rely on rectangular HTML elements.

With a bit of creativity, and a tiny bit of JavaScript, it can be done, as seen on the map of the British Isles located to the right. The idea is to use an image map overlay to define the clickable areas, and positioned list elements below that for the actual roll over effects. Then use a bit of jQuery to tie the two together.

The background image

The background image for the map

As with regular CSS sprites, the background image should include both the 'neutral' state for each area and the 'hover' state. A rectangular box around each area to be highlighted is defined. This representsthe part of the background image that will be displayed on hover. See where the boxes are defined for each area with this map.

For irregular images such as this one, it is impossible to draw these boxes without overlapping other areas that also need a hover state. Thefore, the background image repeats itself enough times for this to happen. In this instance, Scotland and Wales make up the first copy of the background image, and England and Ireland the second. Northern Ireland is placed in the margins because it doesn't quite fit with any other combination of countries. The end result looks like the image to the right.

The display layer

In order to construct the display layer, the first thing that is needed is to show the neural state of the map. A list is created with its background image set as the full map created above. This list is given dimensions such that only the neutral state of the map is shown. A container div is also created. The paragraph tag is there simply as a place to house a bit of descriptive text. It is absolute positioned, and given a z-index such that it overlays the list.

  • <style type="text/css">
    • #mapcontainerDiv {position: relative; z-index: 5; width: 165px; height: 200px;}
    • #mapcontainerDiv p {z-index: 20; position: absolute; left: 0px; top: 0px; font: 0.65em Verdana, Helvetica, Arial, sans-serif; width: 65px; margin: 0; padding: 0; font-weight: bold;}
    • #spritemapUl{ width: 165px; height: 200px; background: url(images/british_full.jpg); margin: 0; padding: 0; position: relative; _font-size: 1px; z-index: 10;}
  • </style>
  • <div id="mapcontainerDiv">
    • <p>Britain and Ireland</p>
    • <ul id="spritemapUl">
    • </ul>
  • </div>

List elements are created for each area. Each list element is given a unique ID and dimensions corresponding to the rectangular boxes determined for that area when constructing the image. They are then and positioned in the correct spot and the background set such that only the desired highlighted area is displayed. They are hidden by default.

  • <style type="text/css">
    • #mapcontainerDiv {position: relative; z-index: 5; width: 165px; height: 200px;}
    • #mapcontainerDiv p {z-index: 20; position: absolute; left: 0px; top: 0px; font: 0.65em Verdana, Helvetica, Arial, sans-serif; width: 65px; margin: 0; padding: 0; font-weight: bold;}
    • #spritemapUl{ width: 165px; height: 200px; background: url(images/british_full.jpg); margin: 0; padding: 0; position: relative; _font-size: 1px; z-index: 10;}
    • #spritemapUl li {margin: 0; padding: 0; list-style: none; display: block; position: absolute; text-indent: -1000em;display: none; background:url(images/british_full.jpg) no-repeat;}
    • #spritemapUl li#Scotland { width: 72px; height: 103px; left: 41px; top: 0px; background-position: -41px -200px;}
    • #spritemapUl li#England {width: 100px; height: 124px; left: 63px; top: 74px; background-position: -63px -474px;}
    • #spritemapUl li#Wales {width: 37px; height: 45px; left: 69px; top: 125px; background-position: -69px -325px;}
    • #spritemapUl li#NorIre { width: 37px; height:29px; left: 33px; top: 88px; background-position: -128px -200px;}
    • #spritemapUl li#Ireland {width: 61px; height:82px; left: 0px; top: 85px; background-position: 0px -485px;}
  • </style>
  • <div id="mapcontainerDiv">
    • <p>Britain and Ireland</p>
    • <ul id="spritemapUl">
      • <li id="Scotland"></li>
      • <li id="England"></li>
      • <li id="Wales"></li>
      • <li id="NorIre"></li>
      • <li id="Ireland"></li>
    • </ul>
  • </div>

The image map

An image map provides the mechanism to match the higlighted area to an irregular shape. Each area within the image map is given a unique id that matches its corresponding list element, only with the addition of "area_" at the beginning for the image map areas. So for Scotland, the list element is id="Scotland" and the image map area is id="area_Scotland". This image map is assigned to a 1px transparent gif that is given a z-index such that it is on top and stretched out match the same dimensions and position of the neutral map of the British Isles.

In this particular example, a couple empty of image map areas are created for the seas between Ireland and Britain purely for usability's sake. The JavaScript displays "Britain and Ireland" when the user is not hovering over an active area. However, when a user moves their mouse from island one to the other, the empty seas between cause the words to flicker annoyingly. The empty image map areas provide a simple way to solve this problem.

  • <style type="text/css">
    • #transparent_map { width: 165px; height: 200px; position: absolute; top: 0px; left: 0x; z-index: 40; border: none;}
    • /*The rest of the styles as defined above*/
  • </style>
  • <map name="britishisles_map" id="britishisles_map">
    • <area shape="poly" coords="LOTS OF NUMBERS" href="ireland.htm" alt="Ireland" id="area_Ireland"/>
    • <area shape="poly" coords="LOTS OF NUMBERS" href="nireland.htm" title="Northern Ireland" alt="Northern Ireland" id="area_NorIre"/>
    • <area shape="poly" coords="LOTS OF NUMBERS" href="scotland.htm" alt="Scotland" id="area_Scotland"/>
    • <area shape="poly" coords="LOTS OF NUMBERS" href="england.htm" alt="England" id="area_England"/>
    • <area shape="poly" coords="LOTS OF NUMBERS" href="wales.htm" title="wales" alt="Wales" id="area_Wales"/>
    • <!--The blank areas for the empty seas-->
    • <area shape="poly" coords="LOTS OF NUMBERS" title=" " alt=" " />
    • <area shape="poly" coords="LOTS OF NUMBERS" title=" " alt=" " />
  • </map>
  • <div id="mapcontainerDiv">
    • <p>Britain and Ireland</p>
    • <img id="transparent_map" alt=" " src="images/transp.gif" useMap="#britishisles_map" />
    • <UL id="spritemapUl">
      • <li id="Scotland"></li>
      • <li id="England"></li>
      • <li id="Wales"></li>
      • <li id="NorIre"></li>
      • <li id="Ireland"></li>
    • </ul>
  • </div>

Putting it all together with jQuery

Finally, a bit of JavaScript is employed to tie the image map to the list elements below. On mousing over an area, the javascript checks to see if that area has an id. If so, it matches it to the corresponding li by getting the area’s ID, stripping "area_" from it, then displays the list element who’s ID matches this variable. It also grabs the area's Alt tag and uses that to replace the text of paragraph.

Upon mousing out of an area, the script hides the corresponding area using a very similar process. The default text for the paragraph is also replaced. With this, the map is complete!

  • $(document).ready(function() {
    • // Loop through each AREA in the imagemap
    • $('#britishisles_map area').each(function() {
      • $(this).click(function(){
        • return false;
      • });
      • // Assigning an action to the mouseover event
      • $(this).mouseover(function() {
        • if($(this).attr('id')){
          • var countyId = $(this).attr('id').replace('area_', '');
          • $("#spritemapUl li[id!=countyId]").hide(u);
          • $('#'+countyId).show();
        • }
      • $("#mapcontainerDiv p").text($(this).attr('alt'));
      • });
      • // Assigning an action to the mouseout event
      • $(this).mouseout(function() {
        • if($(this).attr('id')){
          • var countyId = $(this).attr('id').replace('area_', '');
          • $('#'+countyId).hide();
        • }
        • $("#mapcontainerDiv p").text("Britain and Ireland");
      • });
    • });
  • });

Form Input Descriptor Values

In form design, there are times when it is advantageous to put descriptive text values within form fields without those values impacting the user's data entry the submission processes. For example, if a site has a search feature, putting the text ‘search’ within the field gives the user a very clear indication of what the function of that form is without the need to spend potentially valuable screen real estate on plain text or large buttons. In order to make this usable and flow well, JavaScript or another scripting language is required.

The form to the right provides an example, and the article below describes how this was achieved.

The HTML

In this example, a simple 3 field form is being employed. When setting a form up to be used with this script, the form tag and any input fields accessed by the script need a unique ID. The content class descText meant to cause descriptor text to stand out from regular text, and is placed into the form by the JavaScript.

  • <style type="text/css">
    • .descText {color: #66f;}
  • </style>
  • <form action="" method="post" name="HolyGrailForm" id="HolyGrailForm">
    • <input name="HGFname" id="HGFname" type="text" />
    • <input name="HGFquest" id="HGFquest" type="text" />
    • <input name="HGFcolor" id="HGFcolor" type="text" />
    • <input name="submit" type="submit" value="Submit" />
  • </form>

The data

The first thing that is needed is a way to store information about which fields need what values inserted. I have chosen to use a JSON object for this, as it is very easy to read, manipulate, and expand when additional functionality is desired. The contact form on this site adds required field information and error messages to its data object, for instance.

For this example, fieldID stores the ID of each field to be manipulated and sampleText stores the value to be placed in it. This data structure, and the ID of the form being processed, are then passed into the main function, formDescriptorVals. The rest of this script takes place within that function.

  • $(document).ready(function(){
    • var formSettings = {"fields": [
      • {"fieldID": "HGFname", "sampleText": "What is your name?"},
      • {"fieldID": "HGFquest", "sampleText": "What is your quest?"},
      • {"fieldID": "HGFcolor", "sampleText": "What is your favorite color?"}
    • ]};
    • formDescriptorVals('HolyGrailForm', formSettings);
  • });
  • function formDescriptorVals(currentForm, formSettings){
    • //The rest of the code is inserted within this function
  • }

Placing the descriptor text in the form

The first two variables declared in this section, formID and curFormFields, concatenate some information for convenience.

This script block places the descriptor text into the appropriate fields upon first loading the form. The script loops through the arrays of objects within formSettings using a for/in loop. For each array, the script retrieves the fieldID and places it into a jQuery object. The associated HTML input is then accessed to check and see if there is a value already present within the input. If not, it places the sampleText for that array into the HTML field with that ID.

During this operation, the CSS class descText is added to any field where descriptor text is being inserted.

  • var formID = "#" + currentForm;
  • var curFormFields = formID + " :input" ;
  • for (var key in formSettings.fields){
    • if (formSettings.fields.hasOwnProperty(key)) {
      • var currentField = $("#" + formSettings.fields[key].fieldID);
      • if(currentField.val("")){
        • currentField.val(formSettings.fields[key].sampleText);
        • currentField.addClass("descText");
      • }
    • }
  • }

Creating the focus event

When a user focuses on any field in the form, a check must be performed to see if that field has been defined somewhere in the formSettings data object. A function, checkValue, is called that compares the focused field’s ID against the fieldID data stored in formSettings. If the function finds a match, it returns the key value for that array. This allows the script to directly access that particular array within formSettings.

The sampleText object for this array is then compared to the current value of the associated field. If sampleText data is present, the field’s current value is cleared and the CSS class removed so the user can enter their own data. If no match is found, meaning the user has previously entered a value into this field, no action is taken.

  • $(curFormFields).focus(function () {
    • var curPos = null;
    • curPos = checkValue($(this));
    • if(curPos && formSettings.fields[curPos].sampleText == $(this).val()){
      • $(this).val("");
      • $(this).removeClass("descText");
    • }
  • });
  • function checkValue(curField){
    • var currId = curField.attr("id");
    • for (var key in formSettings.fields){
      • if(formSettings.fields[key].fieldID == currId){
        • return key;
      • }
    • }
  • }

Creating the blur event

When the user leaves the field, a check very similar to the focus event must be made. This time, if the field value is blank and the field has been defined witin formSettings, the sampleText for that field and the css class descText are both replaced.

  • $(curFormFields).blur(function () {
    • if($(this).val() == ""){
      • var curPos = null;
      • curPos = checkValue($(this));
      • if(curPos){
        • $(this).val(formSettings.fields[curPos].sampleText);
        • $(this).addClass("descText");
      • }
    • }
  • });

Creating the submit event

When the form is submitted, any sampeText data still present in any field must be cleared without removing user entered data. The script loops through formSettings and accesses each field with at the ID corresponding to a fieldID stored in an array. If a match is found between the actual value and the sampleText for that field, the sampleText is cleared.

With this, the code is complete and ready to go!

  • $(formID).submit(function(event){
    • for (var key in formSettings.fields){
      • if (formSettings.fields.hasOwnProperty(key)) {
        • var currentField = $("#" + formSettings.fields[key].fieldID);
        • if (currentField.val() == formSettings.fields[key].sampleText){
          • currentField.val("");
        • }
      • }
    • }
  • });