Expanding Image Menu with jQuery

In today’s tutorial we will create an expanding image menu with jQuery. The idea is to have some columns with black and white image slices that will make a content […]

In today’s tutorial we will create an expanding image menu with jQuery. The idea is to have some columns with black and white image slices that will make a content area slide out when we click on them. We will also slide in the colored version of the image, creating a neat effect.

The photography is by talented Robert Bejil, check out his awesome photos on his Flickr photostream.

So, let’s get started!

The Markup

The HTML structure consists of a main container and an unordered list where each item is one of the columns. We will give the main container the class “ei_menu” and also the id “ei_menu”. The list items are going to contain a link element with two spans inside and a div element with the content. The two spans are for the background image shown at the beginning and the colored version of the image for when we click on the item. Since we are using just one image for each we will have to define the background position. We will take care of that with the classes “pos1” to “pos5” that we will give to the parent link element.

<div id="ei_menu" class="ei_menu">
	<ul>
		<li>
			<a href="#" class="pos1">
				<span class="ei_preview"></span>
				<span class="ei_image"></span>
			</a>
			<div class="ei_descr">
				<h2>Gary</h2>
				<h3>Vocals</h3>
				<p>Some text here...</p>
			</div>
		</li>
		<li> ... </li>
	</ul>
</div>

Let’s take a look at the style.

The CSS

We are going to stretch the container for the list over the page and hide the overflow. Since we will adjust the size of the ul, we don’t want anything sticking out because of the slide out effect, to affect our container:

.ei_menu{
	background:#111;
	width:100%;
	overflow:hidden;
}

We will give enough width to the ul so that the li elements which will be floating, don’t wrap to the next “line” when they expand:

.ei_menu ul{
	height:350px;
	margin-left:50px;
	position:relative;
	display:block;
	width:1300px;
}

The overflow of the li elements is going to be hidden as well because our content inside is actually much bigger. And we only want to show that content, once we expand the while thing by increasing the width of the li:

.ei_menu ul li{
	float:left;
	width:75px;
	height:350px;
	position:relative;
	overflow:hidden;
	border-right:2px solid #111;
}

The “ei_preview” span will contain the black and white background image and will be of absolute positioning:

.ei_preview{
	width:75px;
	height:350px;
	cursor:pointer;
	position:absolute;
	top:0px;
	left:0px;
	background:transparent url(../images/bw.jpg) no-repeat top left;
}

The “ei_image” span will have the colored background image and be almost transparent. We will animate its position and also its opacity to create a cool effect:

.ei_image{
	position:absolute;
	left:75px;
	top:0px;
	width:75px;
	height:350px;
	opacity:0.2;
	background:transparent url(../images/color.jpg) no-repeat top left;
}

To define the positioning of the background for both spans, we will use the following classes, that we give to the parent link element:

.pos1 span{
	background-position:0px 0px;
}
.pos2 span{
	background-position:-75px 0px;
}
.pos3 span{
	background-position:-152px 0px;
}
.pos4 span{
	background-position:-227px 0px;
}
.pos5 span{
	background-position:-302px 0px;
}
.pos6 span{
	background-position:-377px 0px;
}

The content div will also have an absolute position and the left value is going to be the width of the spans:

.ei_descr{
	position:absolute;
	width:278px;
	height:310px;
	border-right:7px solid #f0f0f0;
	padding:20px;
	left:75px;
	top:0px;
	background:#fff;
}

We’ll spice up the heading of the content area with a nice grungy font that we’ll get from the Google fonts. We’ll also add some neat background stripes:

.ei_descr h2{
	font-family: 'Rock Salt', arial, serif;
	font-size:26px;
	color:#333;
	padding:10px;
	text-shadow:0px 0px 1px #fff;
	background:#fff url(../images/stripe_light.gif) repeat top left;
}

The sub heading will also have a font from the collection of Google fonts:

.ei_descr h3{
	font-family: 'Raleway', arial, serif;
	color:#fff;
	text-shadow:0px 0px 1px #000;
	font-style:normal;
	padding:10px;
	background:#333;
}

And finally, we style the paragraphs:

.ei_descr p{
	color:#000;
	padding:10px 5px 0px 5px;
	line-height:18px;
	font-size:11px;
	font-family: Arial, sans-serif;
	text-transform:uppercase;
}

And that’s all the style!
Don’t forget to include the fonts that you are using from Google:

<link href='http://fonts.googleapis.com/css?family=Rock+Salt' rel='stylesheet' type='text/css' />
<link href='http://fonts.googleapis.com/css?family=Raleway:100' rel='stylesheet' type='text/css' />

This is added to the head of your HTML file.
And now, let’s spice it up and add the magic!

The JavaScript

The main idea is to expand a menu item when clicking on it. This means that we want to animate the width of the li to 400 pixel (which is its initial width and the width of the spans, 75 pixel, plus the width of the content area which is 325 pixel counting with the padding and the border). While we do that, we also want to slide in the second span which has the colored background image and animate its opacity. We also want to decrease the opacity of the other items in order to enhance the current one. This will look as if they get darkened because we have a dark background.

Let’s begin by caching some elements:

var $menu				= $('#ei_menu > ul'),
	$menuItems			= $menu.children('li'),
	$menuItemsImgWrapper= $menuItems.children('a'),
	$menuItemsPreview	= $menuItemsImgWrapper.children('.ei_preview'),
	totalMenuItems		= $menuItems.length,

And let’s define the functionality:

ExpandingMenu 	= (function(){
	/*
		@current
		set it to the index of the element you want to be opened by default,
		or -1 if you want the menu to be closed initially
	 */
	var current				= -1,
	/*
		@anim
		if you want the default opened item to animate initially, set this to true
	 */
	anim				= true,
	/*
		checks if the current value is valid -
		between 0 and the number of items
	 */
	validCurrent		= function() {
		return (current >= 0 && current < totalMenuItems);
	},
	init				= function() {
			/* show default item if current is set to a valid index */
		if(validCurrent())
			configureMenu();
			
		initEventsHandler();
	},
	configureMenu		= function() {
			/* get the item for the current */
		var $item	= $menuItems.eq(current);
			/* if anim is true, slide out the item */
		if(anim)
			slideOutItem($item, true, 900, 'easeInQuint');
		else{
				/* if not, just show it */
			$item.css({width : '400px'})
			.find('.ei_image')
			.css({left:'0px', opacity:1});
					 
				/* decrease the opacity of the others */	
				$menuItems.not($item)
						  .children('.ei_preview')
						  .css({opacity:0.2});	 
		}
	},
	initEventsHandler	= function() {
			/*
			when we click an item the following can happen:
			1) The item is already opened - close it!
			2) The item is closed - open it! (if another one is opened, close that one!)
			*/
		$menuItemsImgWrapper.bind('click.ExpandingMenu', function(e) {
			var $this 	= $(this).parent(),
			idx		= $this.index();
				
			if(current === idx) {
				slideOutItem($menuItems.eq(current), false, 1500, 'easeOutQuint', true);
				current = -1;
			}
			else{
				if(validCurrent() && current !== idx)
						slideOutItem($menuItems.eq(current), false, 250, 'jswing');
					
				current	= idx;
					slideOutItem($this, true, 250, 'jswing');
			}
			return false;
		});
	},
		/* if you want to trigger the action to open a specific item */
		openItem			= function(idx) {
			$menuItemsImgWrapper.eq(idx).click();
		},
		/*
		opens or closes an item
		note that "mLeave" is just true when all the items close,
		in which case we want that all of them get opacity 1 again.
		"dir" tells us if we are opening or closing an item (true | false)
		*/
	slideOutItem		= function($item, dir, speed, easing, mLeave) {
		var $ei_image	= $item.find('.ei_image'),
				
		itemParam	= (dir) ? {width : '400px'} : {width : '75px'},
		imageParam	= (dir) ? {left : '0px'} : {left : '75px'};
			
			/*
			if opening, we animate the opacity of all the elements to 0.1.
			we do this in order to enhance the opened item more:
			*/
		if(dir)
		/*
				alternative:
				$menuItemsPreview.not($menuItemsPreview.eq(current))
								 .stop()
								 .animate({opacity:0.1}, 500);
		 */
			$menuItemsPreview.stop()
		.animate({opacity:0.1}, 1000);
		else if(mLeave)
			$menuItemsPreview.stop()
		.animate({opacity:1}, 1500);
			
			/* the <li> expands or collapses */
		$item.stop().animate(itemParam, speed, easing);
			/* the image span (color) slides in or out */
		$ei_image.stop().animate(imageParam, speed, easing, function() {
				/* 
				if opening, we animate the opacity to 1, 
				otherwise we reset it.
				*/
			if(dir)
				$ei_image.animate({opacity:1}, 2000);
			else
				$ei_image.css('opacity', 0.2);
		});
	};
		
	return {
		init 		: init,
		openItem	: openItem
	};
})();
	
/*
call the init method of ExpandingMenu
 */
ExpandingMenu.init();

/*
if you want to open / close a specific item you could do it like this:
ExpandingMenu.openItem(3); // toggles item 3 (zero-based indexing)
*/

And that’s all!

Alternative Demos

You can check out the following other demos which all make use of the different options in our script:

All of these versions will be included in the ZIP file.
We hope you enjoyed the tutorial and find it useful!

Tagged with:

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay up to date with the latest web design and development news and relevant updates from Codrops.