Garage Door Style Menu

Chris Coyier //

Originally published on July 21, 2008 as only a jQuery technique. Now updated to include CSS3 and a combo technique which is mo' betta.

A garage door style menu is where an image (the "door") slides up to reveal something behind it. We'll do this in two ways, with CSS3, and with jQuery. Then we'll combine them into a progressive enhancement way to handle it.

The jQuery Method

Right out of the box, jQuery provides the animate function, which allows us to morph some CSS attributes over time. Things like opacity, font-size, width, length, margin, and padding, basically anything where you set the value with a number is supported. There are a couple of notable limitations to this function though, and one of those is "background-position".

Thankfully, there is a plugin to help with this, the Background-Position Animation Plugin by Alexander Farkas. (direct link to JS). With this, we can do some fun stuff! NOTE: This plugin seems to work great with jQuery 1.2.6, but broke when I tried the lastest-as-of-this-writing 1.4.3.

View Demo   Download Files

1. Creating the image needed

We are going to need three different types of images here. First is a background image for the entire menu itself. This is going to be one big image for the entire menu which will be the imagery which is "revealed" when the door opens. Check out my Photoshop file:

Notice my guides. I have these set up as a visual reference to what the "viewable area" is going to be within the garage frame. Note that the Photoshop files are included in the download for your reference. Combining this into one graphic saves HTTP requests, like CSS Sprites.

Second, we'll need to create the garage doors which we will call the "shutters". These need to be separate graphics as each one will need to be animated separately. I made a separate template for this, so I could again use guides to keep things as close to centered and nicely spaced as possible.

Lastly, we need a window which will act as the garage frame. This is the nice touch that really ties the whole idea together. Since this is going to be the top-most layer, we will apply this to the anchor links themselves, so that they can be clickable with unique URLs.

2. Writing the HTML markup

Of coure, no matter how fancy we want to get with our menus, the markup should be clean and semantic so that with CSS and/or JavaScript disabled, the menu still looks and behaves like a menu.

Here is the menu markup:

<ul id="garagedoor">
  <li id="shutter1"><a href="#1">Link 1</a></li>
  <li id="shutter2"><a href="#2">Link 2</a></li>
  <li id="shutter3"><a href="#3">Link 3</a></li>
  <li id="shutter4"><a href="#4">Link 4</a></li>
</ul>

The ID on the menu will give us all the specificity we need. Notice though that each shutter has it's own ID. This is just so that each menu item can have it's own door graphic, so we'll use that as a hook. You could also use something like :nth-child() here, but since we're shooting for good cross-browser compatibility we'll skip that. With CSS turned off, we have a very functional menu:

3. The CSS

Here I'll show you the whole CSS file, and then point out a few things below:

#garagedoor {
  margin: 50px auto;
  list-style: none;
  background: url(../images/menu-bg.jpg);
  width: 800px;
  overflow: auto;	
}

#garagedoor li {
  width: 200px;
  height: 100px;
  display: block;
  float: left;
}

#garagedoor li#shutter1 {
  background: url(../images/shutter-africanplains.jpg) no-repeat; 
}
#garagedoor li#shutter2 {
  background: url(../images/shutter-reptiles.jpg) no-repeat; 
}
#garagedoor li#shutter3 {
  background: url(../images/shutter-aviary.jpg) no-repeat; 
}
#garagedoor li#shutter4 {
  background: url(../images/shutter-arcticzone.jpg) no-repeat; 
}

#garagedoor a {
  width: 200px;
  height: 100px;
  display: block;
  background: url(../images/window.png) no-repeat bottom center;
  text-indent: -9999px;
}

The menu background is applied to the UL itself. Then each list item is set to a specific width and height (same height each individual "reveal" graphic) and floated to the left (for a horizontal menu). The ID values on the LI items are used to apply the separate background graphics only. The anchor links, as I mentioned above, will be the top-most layer and thus use the window overlay. These will need to be set as a block level element, have with and height applied, and use text-indent to kick the text off the page.

4. The jQuery JavaScript

First things first, we include the latest version of jQuery on our page, as well as the plugin I linked to at the top of this article. Then we can write the jQuery JavaScript needed to make the garage door effect happen.

$(function() {

	// Set CSS for old versions of Firefox (Required to use the backgroundPosition js)
	$('#shutter1').css({backgroundPosition: '0px 0px'});
	$('#shutter2').css({backgroundPosition: '0px 0px'});
	$('#shutter3').css({backgroundPosition: '0px 0px'});
	$('#shutter4').css({backgroundPosition: '0px 0px'});

	// Animate the Shutter  
	$("#garagedoor a").hover(function(){
	      $(this).parent().stop().animate({backgroundPosition: '(0px -100px)'}, 500);
	}, function() {
	      $(this).parent().stop().animate({backgroundPosition: '(0px 0px)'}, 500);
	});
	 
 });

Then we bind the "hover" event to each of the menu's anchor links. When the hover event occurs over those links, jQuery finds the parent element, and does the background-position animation on that element. In our case, the LI element, with the unique shutters. Using the callback function of the hover event (e.g. when the mouse leaves the area) we animate the shutter back into position.

We are also using jQuery's .stop() function here to prevent animation queue buildup (rapidly mousing on and off of an element will cause the garage door to open and close over and over even while you have moused away). Using .stop() also prevents the animation from completing entirely if moused out before the animation completes. If you are interested in altering this so that the garage door animation happens in its entirety every time, use this plugin.

And there we have it, a nice looking menu with a pretty neat animated effect using jQuery!

View Demo   Download Files

The CSS3 Method

We just covered using jQuery to accomplish the garage door effect. jQuery is a good fit (although nearly every JavaScript library has animation helpers) because it can make the animation work across all browsers. If we were considering this garage door menu a progressive enhancement to our site, we could accomplish the same thing using CSS3, specifically the transition property. Transitions are more than capable of the same simple animations we were using jQuery for.

With the base that we already have from the jQuery method, we can super easily convert it to the CSS3 method.

  1. Remove all the JavaScript
  2. Change the list elements CSS to include the transition property
  3. Add a hover event to change the background-position
#garagedoor li {
  width: 200px;
  height: 100px;
  display: block;
  float: left;
  -webkit-transition: background-position 0.6s ease;
  -moz-transition: background-position 0.6s ease;
  -ms-transition: background-position 0.6s ease;
  -o-transition: background-position 0.6s ease;
  transition: background-position 0.6s ease;
}

#garagedoor li:hover {
  background-position: 0 -100px !important;
}

Update: In past versions of this article, I ommitted some transition vendor prefixes. For example, I left out -o-, because the current version of Opera at the time supported transitions but not on the background-position property (weird). I have added it back in because it now works. But the more important message is, I probably should have had it in there in the past because it was clearly just a shortcoming that would be fixed in the future.

More on CSS transitions here.

Combining Both CSS3 and jQuery

The most feel-good way to accomplish a technique like this, in my opinion, is to use CSS3 where supported and fallback to a JavaScript method. The best way to handle this: Modernizer! Modernizr is a small JavaScript library you can include on your pages to help identify what that browser is capable of handling.

In our case, we need to know if the browser can handle CSS transitions. Modernizr applies a class to the html element on the page called csstransitions if this is possible. So we just change the selector for the CSS transitions specific code:

/* Modernizer Enabled */
.csstransitions #garagedoor li {
  -webkit-transition: background-position 0.6s ease;
  -moz-transition: background-position 0.6s ease;
  -ms-transition: background-position 0.6s ease;
  -o-transition: background-position 0.6s ease;
  transition: background-position 0.6s ease;
}
.csstransitions #garagedoor li:hover {
  background-position: 0 -100px !important;
}

This ensures that browsers that don't support the transition won't even attempt it. For those browsers, we'll be doing a jQuery-based fallback. You may already be using jQuery on your page for other reasons. In our case, we are not, so let's say that we only want to load jQuery at all if needed for the fallback.

With Modernizr, we'll conditionalize our code:

if (!Modernizr.csstransitions) {
  // do fallback stuff
} 

The trick here is that loading a script within JavaScript is a bit tricky, especially since we can't use jQuery yet. What we'll do is leverage this dynamic loading idea. We will:

  1. Test if jQuery is loaded
  2. If not (won't be on first run)...
  3. - Load the script by writing it to the document
  4. - Go to #1
  5. If jQuery is loaded...
  6. Load the backgroundPosition plugin
  7. Code for doing animation

Here's the load:

var jQueryScriptOutputted = false;

function initJQuery() {
    
    if (typeof(jQuery) == 'undefined') {
    
        if (!jQueryScriptOutputted) {
            jQueryScriptOutputted = true;
            
            // Primitive way of loading scripts (no library yet)
            document.write("<scr" + "ipt src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></scr" + "ipt>");
        }
        setTimeout("initJQuery()", 50);
        
    } else {
    	
    	// jQuery way of loading scripts
    	$.getScript('js/jquery.backgroundPosition.js', function() {
         
            // Set CSS in Firefox (Required to use the backgroundPosition js)
			$('#shutter1').css({backgroundPosition: '0px 0px'});
			$('#shutter2').css({backgroundPosition: '0px 0px'});
			$('#shutter3').css({backgroundPosition: '0px 0px'});
			$('#shutter4').css({backgroundPosition: '0px 0px'});

			// Animate the Shutter  
			$("#garagedoor a").hover(function() {	
			      $(this).parent().stop().animate({backgroundPosition: '(0px -100px)'}, 500);
			    }, function() {
			      $(this).parent().stop().animate({backgroundPosition: '(0px 0px)'}, 500);
			});
			
    	});

    }
            
}

if (!Modernizr.csstransitions) {
  initJQuery();
}

Enjoy

You know the drill people. Do whatever you want with this, preferably use it in a big corporate project with no credit and get rich.

View Demo   Download Files