Background
This article was inspired by Jason Kottke who recently installed a CSS- and DOM-based drop-down list on his homepage.
The approach taken is that the list, a <ul>
, is initially declared with display: none;
in CSS. A block element (that represents the ‘landing zone’) has a DOM call attached to its onmouseover
event that changes the display
attribute of the <ul>
’s style to block
. The <ul>
, in turn, has its onmouseout
attribute set to return the style’s display
attribute back to none
.
The problem
Unfortunately, in Firefox for Windows the drop-down suffers from wild flickering as you move the mouse down the list. This is not ideal and it got me working on a solution …
After a spell of tinkering with my take on Jason’s drop-down list, I established that the problem wasn’t only confined to Firefox but to Mozilla 1.7.7 as well — so it’s a fair bet that it’s a problem on all Gecko-based browsers, for Windows at least (it isn’t a problem on Firefox for BeOS).
The problem appears to be related to the way Gecko handles the onmouseout
event. As the mouse is moved down the list the onmouseout
event is called for the <ul>
even though the mouse hasn’t left the confines of the <ul>
. The effect is that the drop-down is briefly hidden then re-shown — hence the flicker. It’s possible it has something to do with the mouse entering the area taken up by each <li>
that causes the onmouseout
to trigger. In any case there is a solution:
The solution
The answer is to take advantage of the browsers’ innate ability to trigger the showing/hiding of the list via CSS rather than the DOM. This is achieved by using the :hover
pseudo-class on a containing block element.
Suppose the containing block has an id
of dropdown and the <ul>
has an id
of droplist:
<div id="dropdown"> <span>My dropdown list</span> <ul id="droplist"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> <li>Item 4</li> <li>Item 5</li> </ul> </div>
We can cause the droplist to appear by setting the following CSS rule:
#dropdown:hover droplist { display: block; }
This will reveal the list as the mouse moves over the dropdown element and will remain showing as long as the mouse remains over (the now enlarged) dropdown.
However, there’s just a tiny problem with this scheme! Internet Explorer, bless it’s little cotton socks, doesn’t know what to make of :hover
on any element other than <a>
, so it ignores it and the drop-down does not drop down.
After much preamble, then, we’re at the heart of the solution. The idea is to use the onmouseover
and onmouseout
events only if there’s no other choice. This means that browsers that know how to :hover
can do their native thing and those that don’t can use the DOM.
So, how do we know if the browser we’re dealing can play dice? The answer is a DOM routine that is called from the page’s onload
attribute and a global variable to keep track of whether, or not, the browser knows how to :hover
—
<script type="text/javascript"> //<!-- //<![CDATA[ var g_bH = false; function init(p_strId) { g_bH = false; var l_E = document.getElementById(p_strId); if(l_E && document.defaultView) { if(document.defaultView.<span class="exDesc">⇒</span> getComputedStyle(l_E, 'hover')) { g_bH = true; } } l_E = null; } //]]> //--> </script>
(The ⇒ signifies that the line would, but for display considerations, continue on the same line.)
The script shown here would be placed in the <head>
block of the page, but could just as well be included from a linked JavaScript file.
The page’s <body>
would read:
<body onload="init('dropdown');">
The code fragment if(document.defaultView.getComputedStyle(l_E, 'hover')) {
relies on two things: firstly that the element’s :hover
attribute appears in the CSS
#dropdown:hover droplist {...}
in this case and, secondly, that the browser implements the document.defaultView.getComputedStyle()
directive (which IE cannot).
If the :hover
declaration is in the CSS and the browser understands then we set a global variable g_bH to record the result. We could call a function every time we wanted to know, but it’s more efficient to set a global since the CSS entry and the browser’s capability won’t change underneath us.
Now it’s just a simple matter of adding the onmouseover
and onmouseout
attributes to the dropdown <div>
:
<div id="dropdown" onmouseout="if(!g_bH){document.<span class="exDesc">⇒</span> getElementById('droplist').style.display='none';}" onmouseover="if(!g_bH){document.<span class="exDesc">⇒</span> getElementById('droplist').style.display='block';}">
Now, when the mouse hovers over the dropdown <div>
either the browser will show the droplist <ul>
because it is responding the the CSS rule, or set the droplist’s style.display
if if can not. When the mouse leaves the dropdown again the CSS rule will be envoked if the variable g_bH is false, or the style.display
will be set if not.
See the accompanying example for details of how this could be implemented. Feel free to use this implementation if you find it of any use.