Create a really simple fly-out menu

Recently I had a need to for a really simple menu to disclose some secondary options - you might think of it as a revealing interface.

There are a millions of flyout, drop down, suckerfish menu generators out there, but all of them are really complex. I just need to show a few options.

Here is what I came up with:

Instead of pasting a bunch of markup into this post, have a look at the live example.

How it works

The markup is super simple. There is a link used to trigger the menu, and the menu itself is an unordered list with some links it.

Scripting

The scripting is also very straightforward (I’m using jQuery, but there is nothing about it that is jQuery specific). Listen for a the .trigger link to get clicked, and display the closest .menu if it’s closed:

var $menu = $('ul.menu_options');

//hide all of the menus
$menu.hide();

$('a.trigger').each(function(index) {
	$(this).click(function() {
		//identify the clostest menu
		$nextMenu = $(this).next($menu);
		if ($nextMenu.is(":visible")) {
			$nextMenu.fadeOut('fast').css('z-index', '50');
		} else {
			$menu.hide(); //close all other open menus
			$nextMenu.show().css('z-index', '500'); //z-index for good measure
		}

		return false;
	});
});

That’s probably good enough for most uses - but I can’t leave good enough alone. The weakness with is that the menu hangs around until you click the trigger again to close it.

Here is the ideal behavior:

  • If you trigger the menu and do nothing, it will go away after n seconds.
  • If you trigger the menu and mouseover it, it will stay there.
  • When you mouse leaves the menu, it will go away after n seconds.

To achieve this, we’ll set a unique setTimeout for each menu and listen for mouseenter and mouseleave events to clear the timeout. Here it is all together:

var $menu = $('ul.menu_options');
var $open = $('a.trigger');

//create an emtpy object to keep track of the timeouts
var menuTimeouts = {};

//hide all of the menus
$menu.hide();

$open.each(function(index) {
	$(this).click(function() {
		//identify the clostest menu
		$nextMenu = $(this).next($menu);
		if ($nextMenu.is(":visible")) {
			$nextMenu.fadeOut('fast').css('z-index', '50');

                        //clear the active setTimout
			if (menuTimeouts[$id]) {
				clearTimeout(menuTimeouts[$nextMenu.attr('id')]);
			        menuTimeouts[$nextMenu.attr('id')] = undefined;
	                 }
		} else {
			$menu.hide(); //close all other open menus
			$nextMenu.show().css('z-index', '500'); //z-index for good measure

			//start a unique setTimeout for this menu
			menuTimeouts[$nextMenu.attr('id')] = setTimeout(function () {
				//fade the menu out after 2 seconds
				$nextMenu.fadeOut('slow');
			}, 2000)
		}

		return false;
	});
});

$menu.find('li').mouseenter(function (event) {
	//store the ID of this trigger's closest menu
	var $id = $(this).parents('ul').attr('id');

	//cancel the timeOut when the mouse is over the menu
	if (menuTimeouts[$id]) {
		clearTimeout(menuTimeouts[$id]);
		menuTimeouts[$id] = undefined;
	}
}).mouseleave(function () {
	var $this = $(this).parents('ul');

	//start another time when the mouse leaves the menu
	menuTimeouts[$this.attr('id')] = setTimeout(function () {
		$this.fadeOut('slow');
	}, 2000);
});

Checkout live example to see it all in action →

I’ve tested this in Firefox, Safari, and IE7 and IE8. You might need to tweak the position of the menu in IE, but everything else is good to go.