Animated Content Menu with jQuery

Today we will create a slick animated content menu with jQuery for a restaurant theme. The menu items will be animated and when clicked, a content area with more information will appear. Also, he background image is going to change according to which menu item was clicked. The main idea is to have a restaurant […]
AnimatedContentMenu

From our monthly sponsor: Automate manual QA and catch visual bugs with Percy’s all-in-one visual testing and review platform.

Today we will create a slick animated content menu with jQuery for a restaurant theme. The menu items will be animated and when clicked, a content area with more information will appear. Also, he background image is going to change according to which menu item was clicked.

The main idea is to have a restaurant menu with the different menu categories displayed. Each content are will have some foods/drinks listed. Once a content box is shown, the menu items are going to disappear. If we click on the close button to close the box, the menu items will be shown again.

So, let’s get started.

The Markup

The HTML structure is going to contain some elements for the background image, the grid overlay, the loading icon and the main menu. The Markup for the background is going to look like this:

<div id="ac_background" class="ac_background">
	<img class="ac_bgimage" src="images/Default.jpg" alt="Background"/>
	<div class="ac_overlay"></div>
	<div class="ac_loading"></div>
</div>

As a default background image when the page loads, we will have the image Default.jpg. Whenever we click on a menu item, we are going to change that image by adding another one and making the current one disappear.

The menu content will be wrapped by a div with the class “ac_content”. There will be a title and an unordered list for the items wrapped by a div with the class “ac_menu”:

<div id="ac_content" class="ac_content">
	<h1><span>Cafe + Bar</span>Dhalia</h1>
	<div class="ac_menu">
		<ul>
			<li>
				<a href="images/Appetizers.jpg">Appetizers</a>
				<div class="ac_subitem">
					<span class="ac_close"></span>
					<h2>Appetizers</h2>
					<ul>
						<li>
							A wonderful serenity has taken possession 
							of my entire soul, like these sweet mornings 
							of spring which I enjoy with my whole heart.
						</li>
						<li>Lobster Bisque</li>
						<li>Smoked Salmon Terrine</li>
						<li>Tuna Ceviche</li>
						<li>Wild Mushroom Flan</li>
						<li>Almond Bruschetta</li>
						<li>Green Chilli Canapee</li>
						<li>Artichoke Rucula Salad</li>
					</ul>
				</div><!-- ac_subitem-->
			</li>
			...
		</ul>
	</div><!-- ac_menu -->
</div><!-- ac_content -->

For each menu item, we will have a div “ac_subitem” which will contain the submenu content box. Inside of that box we will place another list (but it can be any other kind of content).

The link element in the main menu will have a href attribute pointing to the image that we will make appear in the background then.

Let’s take a look at the style.

The CSS

In the beginning of our CSS we will import a reset.css:

@import url('reset.css');

We will make the body black since we want to fade out the background images and make them appear darker while we do that:

body{
	background:#000;
	color:#fff;
	font-family: 'PT Sans Narrow', Arial, sans-serif;
	text-transform:uppercase;
}

The links are going to be white:

a{
	color:#fff;
	text-decoration:none;
}

The background image is going to be fixed and we don’t want to show it in the beginning – we want to take care of the appearance using JavaScript. The positioning and the size of the image is also going to be done dynamically, since we want to place it regarding to the size of the user’s window:

img.ac_bgimage{
	position:fixed;
	left:0px;
	top:0px;
	opacity:0.8;
	display:none;
}

The overlay is going to be placed on top of the image. It’s a repeated pattern that will give the image a neat look:

.ac_overlay{
	width:100%;
	height:100%;
	position:fixed;
	top:0px;
	left:0px;
	background:transparent url(../images/pattern.png) repeat top left;
}

We will place the loading icon in the top right corner of the page and it will appear in the beginning while we load the images:

.ac_loading{
	position:fixed;
	top:10px;
	right:10px;
	background:#000 url(../images/loader.gif) no-repeat center center;
	width:50px;
	height:50px;
	border-radius:10px 10px 10px 10px;
	z-index:999;
	opacity:0.7;
	display:none;
}

The main content div will also be fixed and placed in the middle of the screen. We set the top value to 50% and subtract half of its height from that position by setting the margin to -65 pixel. Like that we place it exactly in the middle of the webpage:

.ac_content{
	position:fixed;
	height:90px;
	width:100%;
	top:50%;
	left:0px;
	margin-top:-65px;
}

The heading to the left will have a black semi-transparent background and we will style the h1 and the span differently. We will also add a 1 pixel margin to the right in order to separate it a bit from the menu wrapper:

.ac_content h1{
	background:transparent url(../images/bg_menu.png) repeat top left;
	display:block;
	float:left;
	width:90px;
	height:50px;
	padding:20px;
	font-size:36px;
	font-weight:bold;
	line-height:20px;
	margin-right:1px;
}
.ac_content h1 span{
	display:block;
	font-weight:normal;
	font-size:14px;
}

The main menu wrapper will have the same background like the heading and we will give it an initial width of 0 pixel. In the JavaScript we will then animate the width when we load the page to fit the width of the window:

.ac_menu{
	background:transparent url(../images/bg_menu.png) repeat top left;
	float:left;
	position:relative;
	height:90px;
	width:0px; 
}

The unordered list which will contain the menu items will float right:

.ac_menu > ul{
	float:right;
}

The list items will have a specified height and the overflow will be set to hidden since we want to animate the link elements to appear from the bottom. If we wouldn’t set the overflow to hidden, we would be able to see the link elements under the menu.

.ac_menu > ul > li{
	float:left;
	position:relative;
	height:90px;
	overflow:hidden;
}

The link elements will initially be hidden by setting the margin top to 60 pixel and the opacity to 0. We will animate that margin to 0 pixel and make them opaque, in order for the items to appear, each one with a little delay, creating an interesting effect.

.ac_menu > ul > li a{
	margin-top:60px;
	opacity:0;
	display:block;
	height:90px;
	padding:0px 10px;
	text-align:center;
	line-height:90px;
	outline:none;
	font-size:18px;
	font-weight:bold;
	text-shadow:1px 1px 1px #000;
}

The subitem boxes will have a width of 400 pixel and an initial height of 0 pixel. We will animate that height and the top margin in order for each box to appear from the middle:

.ac_subitem{
	width:400px;
	height:0px; /* animate to 400px */
	top:50%;
	right:0px;
	margin-top:0px; /* animate to -200px */
	position:fixed;
	z-index:99;
	overflow:hidden;
	background:transparent url(../images/bg_menu.png) repeat top left;
}

Let’s give some styling to the content of the subitems:

.ac_subitem h2{
	font-size:22px;
	font-weight:bold;
	color:#fff;
	padding: 40px 0px 0px 40px;
	text-shadow:0px 0px 1px #000;
}
.ac_subitem ul{
	padding:0px 40px;
}
.ac_subitem ul li{
	margin:10px 0px;
}
.ac_subitem ul li:first-child{
	font-size:14px;
	text-transform:none;
	border-bottom:1px dotted #333;
	padding-bottom:15px;
	margin-bottom:15px;
}

And let’s place the little closing cross at the top right corner:

span.ac_close{
	float:right;
	margin:10px;
	width:11px;
	height:12px;
	cursor:pointer;
	background:transparent url(../images/close.png) no-repeat top left;
	opacity:0.4;
}
span.ac_close:hover{
	opacity:1.0;
}

And that’s all the style. Let’s add the magic!

The JavaScript

For the effects will will be using some easing, so we will need to include the jQuery Easing Plugin after including the jQuery script.

Let’s first cache some elements:

var $ac_background	= $('#ac_background'),
$ac_bgimage		= $ac_background.find('.ac_bgimage'),
$ac_loading		= $ac_background.find('.ac_loading'),

$ac_content		= $('#ac_content'),
$title			= $ac_content.find('h1'),
$menu			= $ac_content.find('.ac_menu'),
$mainNav		= $menu.find('ul:first'),
$menuItems		= $mainNav.children('li'),
totalItems		= $menuItems.length,
$ItemImages		= new Array();

We want to preload all the image, so let’s add all the image sources that we have in the href attributes of the link elements in the menu items, and also the one of the current image:

$menuItems.each(function(i) {
	$ItemImages.push($(this).children('a:first').attr('href'));
});
$ItemImages.push($ac_bgimage.attr('src'));

Then, let’s define our main function:

var Menu 			= (function(){
	var init				= function() {
		loadPage();
		initWindowEvent();
	},
	loadPage			= function() {
		/*
			1- loads the bg image and all the item images;
			2- shows the bg image;
			3- shows / slides out the menu;
			4- shows the menu items;
			5- initializes the menu items events
		 */
		$ac_loading.show(); //show loading status image
		$.when(loadImages()).done(function(){
			$.when(showBGImage()).done(function(){
				//hide the loading status image
				$ac_loading.hide();
				$.when(slideOutMenu()).done(function(){
						$.when(toggleMenuItems('up')).done(function(){
						initEventsSubMenu();
					});
				});
			});
		});
	},
	showBGImage			= function() {
		return $.Deferred(
		function(dfd) {
			//adjusts the dimensions of the image to fit the screen
			adjustImageSize($ac_bgimage);
			$ac_bgimage.fadeIn(1000, dfd.resolve);
		}
	).promise();
	},
	slideOutMenu		= function() {
		/* calculate new width for the menu */
		var new_w	= $(window).width() - $title.outerWidth(true);
		return $.Deferred(
		function(dfd) {
			//slides out the menu
			$menu.stop()
			.animate({
				width	: new_w + 'px'
			}, 700, dfd.resolve);
		}
	).promise();
	},
		/* shows / hides the menu items */
		toggleMenuItems		= function(dir) {
		return $.Deferred(
		function(dfd) {
			/*
			slides in / out the items. 
			different animation time for each one.
			*/
			$menuItems.each(function(i) {
						var $el_title	= $(this).children('a:first'),
							marginTop, opacity, easing;
						if(dir === 'up'){
							marginTop	= '0px';
							opacity		= 1;
							easing		= 'easeOutBack';
						}
						else if(dir === 'down'){
							marginTop	= '60px';
							opacity		= 0;
							easing		= 'easeInBack';
		}
				$el_title.stop()
				.animate({
									marginTop	: marginTop,
									opacity		: opacity
								 }, 200 + i * 200 , easing, function(){
					if(i === totalItems - 1)
						dfd.resolve();
				});
			});
		}
	).promise();
	},
	initEventsSubMenu	= function() {
		$menuItems.each(function(i) {
			var $item		= $(this), // the <li>
			$el_title	= $item.children('a:first'),
			el_image	= $el_title.attr('href'),
			$sub_menu	= $item.find('.ac_subitem'),
			$ac_close	= $sub_menu.find('.ac_close');
			
			/* user clicks on a menu item */
			$el_title.bind('click.Menu', function(e) {
					$.when(toggleMenuItems('down')).done(function(){
					openSubMenu($item, $sub_menu, el_image);
				});
				return false;
			});
			/* closes the submenu */
			$ac_close.bind('click.Menu', function(e) {
				closeSubMenu($sub_menu);
				return false;
			});
		});
	},
	openSubMenu			= function($item, $sub_menu, el_image) {
		$sub_menu.stop()
		.animate({
			height		: '400px',
			marginTop	: '-200px'
		}, 400, function() {
			//the bg image changes
			showItemImage(el_image);
		});
	},
	/* changes the background image */
	showItemImage		= function(source) {
		//if its the current one return
		if($ac_bgimage.attr('src') === source)
			return false;
				
		var $itemImage = $('<img src="'+source+'" alt="Background" class="ac_bgimage"/>');
		$itemImage.insertBefore($ac_bgimage);
		adjustImageSize($itemImage);
		$ac_bgimage.fadeOut(1500, function() {
			$(this).remove();
			$ac_bgimage = $itemImage;
		});
		$itemImage.fadeIn(1500);
	},
	closeSubMenu		= function($sub_menu) {
		$sub_menu.stop()
		.animate({
			height		: '0px',
			marginTop	: '0px'
		}, 400, function() {
			//show items
						toggleMenuItems('up');
		});
	},
	/*
	on window resize, ajust the bg image dimentions,
	and recalculate the menus width
	*/
	initWindowEvent		= function() {
		/* on window resize set the width for the menu */
		$(window).bind('resize.Menu' , function(e) {
			adjustImageSize($ac_bgimage);
			/* calculate new width for the menu */
			var new_w	= $(window).width() - $title.outerWidth(true);
			$menu.css('width', new_w + 'px');
		});
	},
	/* makes an image "fullscreen" and centered */
	adjustImageSize		= function($img) {
		var w_w	= $(window).width(),
		w_h	= $(window).height(),
		r_w	= w_h / w_w,
		i_w	= $img.width(),
		i_h	= $img.height(),
		r_i	= i_h / i_w,
		new_w,new_h,
		new_left,new_top;
			
		if(r_w > r_i){
			new_h	= w_h;
			new_w	= w_h / r_i;
		}
		else{
			new_h	= w_w * r_i;
			new_w	= w_w;
		}
			
		$img.css({
			width	: new_w + 'px',
			height	: new_h + 'px',
			left	: (w_w - new_w) / 2 + 'px',
			top		: (w_h - new_h) / 2 + 'px'
		});
	},
	/* preloads a set of images */
	loadImages			= function() {
		return $.Deferred(
		function(dfd) {
			var total_images 	= $ItemImages.length,
			loaded			= 0;
			for(var i = 0; i < total_images; ++i){
				$('<img/>').load(function() {
					++loaded;
					if(loaded === total_images)
						dfd.resolve();
				}).attr('src' , $ItemImages[i]);
			}
		}
	).promise();
	};
		
	return {
		init : init
	};
})();

/*
call the init method of Menu
 */
Menu.init();

And that’s all! I hope you enjoyed the tutorial and find it useful!

Tagged with:

Mary Lou

ML is a freelance web designer and developer with a passion for interaction design. She studied Cognitive Science and Computational Logic and has a weakness for the smell of freshly ground peppercorns.

http://www.codrops.com

Receive our bi-weekly Collective or official newsletter right in your inbox.

CSS Reference

Learn about all important CSS properties from the basics with our extensive and easy-to-read CSS Reference.

It doesn't matter if you are a beginner or intermediate, start learning CSS now.

Feedback 178

Comments are closed.
  1. Hello Mary Lou,
    how do I add a hyperlink in the text inside . ac_subitem { ?
    Thanks!

  2. Congratulations, beautiful work.
    I’m doing some tests, tried to put the colorbox to work together due to limitation of resolution of some screens, but not getting success. Can you tell me if there is any way to get the two work together? I’ve tried the no conflict, but got no success.

    And once again, congratulations!

  3. Hi MaryLou,

    You are my hero! Thank you and the Codrops team so much for all you do!

    I am working on a rebuild of a site for a restaurant, and was going to use this as a base. When I test it on my computer, it works great. However, when I upload it to there server to test, my lightbox & panelscoller don’t work. Can anyone please tell me what I might be missing?

    The site is in preliminary construction phases right now…but you can take a look at it here:
    http://www.saffronsrestaurant.com/saffrons2011/index01.html

    Thanks again CoDrops! And thanks for anyone who can help me!

  4. Bonjour Mary-Lou,
    Thanks for this wonderful work, it’s simply what I was searching since a looooong time for my personal photo site!
    As I am a newbie in web programing, I ask myself (and your’s now !) how it is possible to make a hyperlink in the primary menu that don’t open a box, but just open a different windows…
    If you have the answer, tell me…
    And once again: thanks !

  5. Nicolas, have you tried making your links open as _blank?

    Also, for anyone else who might be having problems getting alightbox gallery to work, try creating an external link for your jquery library…instead of pulling from “js/jquery.lighbox-0.5.min.js”, use the actual http link (so it would be src=”http://www.yourssitehere.com/js/jquery.lightbox-05.min.js”

    Hope this helps! Cheers 🙂

    Hope this helps! Cheers

  6. Thanks Grafixdiva for your help, but reading my own post, I understood that I mismatch… I said “a different window”, but I was talking about a different page. My idea is to click on a link of the horizontal menu that opens a new single page. That’s why I don’t need to open a “box” in the index page…
    I am very confuse about my wrong question. Maybe, if you consider that I’m french, you will forgive me and my damned english !
    Thanks again !

  7. Kerim, it works fine in Safari version 5.0.4 on Windows. Which version do you have?
    Cheers, ML

  8. hye Mary Lou,

    Thanks for this amazing tutorial. however it’s not working in IE. do you have any idea to fix this prob? hope u have a solution for this. thanks again 🙂

  9. @Allie it works fine in IE9. What exactly is not working for you and which version of IE are you using?
    Cheers,
    ML

  10. thanks ML, im using IE7, should be no prob rite? i think my code got mix up already. here i share my URL, so that u can have a look at my prob. sorry to trouble u, but it would be great if u could help! 🙂 thanks!!

  11. hello, I wanted to know how to enter the main menu of links that take you to another page? no sub menu..tank you

  12. Hey i have a question.
    I want just to use the menu slide in, so whitout the images loading first.Because i want to make site whit multiple pages. What part of the code i need to leave away?

    Thanks

  13. Hi Mary Lou,
    great template! I have a question…
    Is possible to change the height of “ac_subitem” indipendently?
    I need to modify size in relation to every single content.

    Tnx

  14. Wonderful tutorial, Mary!

    There’s only one thing that doesn’t work properly (in my Firefox 4 on Windows): If I slightly increase the font size of my browser (‘CTRL’ + ‘+’ instead of ‘CTRL’ + ‘0’) the menu’s width is 1 or more pixels too big so it appears unter the h1 element.