One method of providing additional options to users is to present a menu that displays itself when necessary, such as when users click or just hover over an item. This article explains how to produce such a DHTML menu, which dynamically positions itself where the user’s mouse is. There are many situations where user experience can be improved on a website by using dynamic menus. These can be created and used in a variety of ways, but I’m going to use a layer or <div> containing information or links on an web page that can be displayed or hidden dynamically. A few examples of this type of script are (1) the advertisements that appear when you move your mouse over certain keywords on the Developer Shed websites, (2) fly-out menus in a website’s navigation area, providing links to sub-sections of the site, or (3) a calendar that displays itself, allowing you to select a date to be entered in a form field. These are the most common applications of the technique, but there are many more.
It’s rarely practical to hard-code the position of such a menu, as the landscape of the page may fluctuate constantly based on the browser resolution, or even the amount of content on the page, in the case of a dynamic, data-fed web site. It is also far too tedious to maintain a separate style sheet with all positioning information based on an array of monitor resolutions. It would be very handy to have the ability to determine the location of where the menu should be, and tell it to display itself there.
That is the purpose of this article. Through the use of DHTML (XHTML, CSS, & JavaScript), we will create a visually appealing menu that will appear slightly offset to the mouse location. The methods I describe will be easily extensible for any use, or any number of menus.
The XHTML
The example I’ll use is that of a clothing store. This store offers three products: hats, shirts, and socks. The user will be able to click on an image map (or a menu bar if you want), and be taken to information for that product.
The socks however, come in a variety of color options. We could simply direct a user to another page from which they navigate to the page for the color they want. But our experience as web designers and usability ‘experts’ dictates that we need to eliminate all unnecessary clicks, making the navigation path as short as possible. We can eliminate that click by showing them a quick menu, and take them directly to the specific page.
Here’s the (X)HTML for the image map. I’ve extracted the style information and the JavaScript, and will explain them each separately.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <script type="text/javascript"> /* JavaScript section of article */ </script> <title>DHTML Layers</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <style type="text/css"> <!-- /* css section of article */ --> </style> <p align="center"><img alt="clothes we offer" src="/images/person.jpg" height="420" width="250" usemap="#clothesMap" border="0" /></p> <map id="clothesMap" name="clothesMap"> <area onmouseover="displayObject('sockOptions', false);" shape="CIRCLE" alt="Hat" coords="123,40,23" href="hat.htm"> <area onmouseover="displayObject('sockOptions', false);" shape="CIRCLE" alt="Shirt" coords="127,146,58" href="shirt.htm"> <area onmouseover="moveObject('sockOptions', event);" shape="CIRCLE" alt="Socks" coords="152,368,22" href="socks.htm"> </map> <div class="options" id="sockOptions" style="left: 10px; width: 80px; position: absolute; top: 30px; height: 40px"> <a href="socks1.htm">Green socks</a><br /> <a href="socks2.htm">White socks</a><br /> <a href="socks3.htm">Plaid socks</a> </div> </body> </html>
Nothing earth shattering here. There’s a basic image map, which is presumably a person wearing the available hat, shirt, and socks (and hopefully more). When you click on the hat or shirt area, you’re taken to those pages. However, when you move your mouse over the socks, that’s when the magic happens. The moveObject function is called, with ‘sockOptions’ and ‘event’ as the parameters. I’ll explain these later. I also made sure for usability’s sake that when you move your mouse over any other area, the sock menus returns to its invisible state.
It’s important to note that if you were to click on the socks, you’d still be directed to a generic - or parent – socks page. This is to ensure that even those visitors who don’t have standards compliant browsers are still able to eventually navigate to the socks they want, they just have to suffer through that one extra click.
Now I’ll quickly explain the styling of the invisible menus.
The CSS
It would be beneficial to specify a class for these menus, rather than a definition for the specific menu. This is because there’s a very good chance that you’ll re-use these menus quite extensively. So here’s the basic style definition:
.options { font-family: arial; font-size: 11px; border: solid 1px #999; font-size:11px; background-color: #fff; z-index:2; padding: 2px; visibility: hidden; display: none; }
So this gives us a box that would stand out and above other elements on the page, with a solid background and border, as well as a higher z-index. I say ‘would’ because we’ve also defined it to be invisible. The inline style defines the absolute positioning, but that really doesn’t matter, as it will be changed by the JavaScript. The following styles are optional, but recommended if the layer is menu style, as it serves to differentiate the item links, make it more apparent that it’s a menu of links rather than a simple box of text.
.options a:link { padding: 1px; text-decoration: none; } .options a:hover { background-color:#CCC; }
This way each link is individually highlighted as the mouse moves over it. Now, let’s move into the code that will actually turn this HTML into DHTML!
To accomplish this task, I’ve split the areas of functionality into three separate methods, one for locating the object in the DOM, one for detecting the mouse position and moving the layer, and finally one for displaying or hiding the layer.
It could all be lumped together in one big method, but for the sake of modularity and re-usability, it is just easiest and most maintenance-friendly to split it up. I’ll explain the methods one at a time, line by line. First of all, the function to retrieve the DOM reference to the object:
function getObject( obj ) {
// step 1 if ( document.getElementById ) { obj = document.getElementById( obj );
// step 2 } else if ( document.all ) { obj = document.all.item( obj );
//step 3 } else { obj = null; }
//step 4 return obj; }
Steps explained:
Here we look for the object with the standards friendly reference, with its element ID. Most if not all new browsers will simply locate the object here, and go straight to step 4
document.all is only if we have to, perhaps with Internet Explorer 4.0. Here you could add in any number of successive ‘else if’ statements to locate the object in any probable way imaginable, such as in other frames. For the sake of simplicity, I’ll leave it at one recourse.
The object could not be found in any of the possible methods, so we return a null reference, instead of simply the string value that was passed in. This way we can build null checks into any consuming methods.
The reference is returned, the function exits. Now, here’s the function to capture the mouse position and move the layer. For Mozilla based browsers, it’s important that ‘e’ is one of the parameters. As we saw in the HTML code, e represents the event that fires the method, in this case ‘onmouseover’. Once again, I’ll outline the steps afterwards.
function moveObject( obj, e ) {
// step 1 var tempX = 0; var tempY = 0; var offset = 5; var objHolder = obj;
// step 2 obj = getObject( obj ); if (obj==null) return;
// step 3 if (document.all) { tempX = event.clientX + document.body.scrollLeft; tempY = event.clientY + document.body.scrollTop; } else { tempX = e.pageX; tempY = e.pageY; }
// step 4 if (tempX < 0){tempX = 0} if (tempY < 0){tempY = 0}
// step 5 obj.style.top = (tempY + offset) + 'px'; obj.style.left = (tempX + offset) + 'px';
// step 6 displayObject( objHolder, true ); }
Steps explained:
Variables are declared to hold mouse coordinates, and set the layer position accordingly. The offset variable is optional, you can set it to ‘0’ if you want the menu to appear precisely where the mouse is. Also, an ‘objHolder’ keeps the original reference to the object name for use later in the function.
An object reference is retrieved. If it can’t be located, a null reference is returned and the function exits to prevent script errors. If this is happening to you, check your spelling of the layer ID both in the ID attribute, and the parameter being passed into the method, make sure they’re precisely the same, including casing.
The mouse coordinates are retrieved. Different methods are used by IE and other browsers. Perhaps with IE 7 this will be rectified, but until then we’re forced to code for both.
This is simple error prevention. We wouldn’t want the menu appearing off the screen, so we make sure that the absolute minimum value the left or top position can have is 0.
Here the retrieved values are added to the default offset, turned into a string, and set as the pixel location of the menu.
The menu is now changed from invisible to visible, showing up in its new, mouse-determined position. Last but not least, here’s the simple function to toggle a layers visibility:
function displayObject( obj, show ) {
// step 1 obj = getObject( obj ); if (obj==null) return;
// step 2 obj.style.display = show ? 'block' : 'none'; obj.style.visibility = show ? 'visible' : 'hidden'; }
Steps Explained:
Same as before, an object reference is retrieved. If it can’t be located, a null reference is returned and the function exits to prevent script errors.
The two important modifiers are display and visibility. They are set to either ‘block’ and ‘visible’, or ‘none’ and ‘hidden’ depending on whether the ‘show’ Boolean flag is true or false. You can reuse this code in many places.
|