Firefox/Mozilla flicker-free drop-down

20 May 2005

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 —

&lt;script type="text/javascript"&gt; //&lt;!-- //&lt;![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">&rArr;</span> getComputedStyle(l_E, 'hover')) {   g_bH = true; }     }     l_E = null;   } //]]&gt; //--&gt; &lt;/script&gt; &nbsp; 

(The signifies that the line would, but for display considerations, continue on the same line.)

The script shown here would be placed in the &lt;head&gt; block of the page, but could just as well be included from a linked JavaScript file.

The page’s &lt;body&gt; would read:

&lt;body onload="init('dropdown');"&gt; &nbsp; 

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 {...} &nbsp; 

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 &lt;div&gt;:

&lt;div id="dropdown"  onmouseout="if(!g_bH){document.<span class="exDesc">&rArr;</span> getElementById('droplist').style.display='none';}"  onmouseover="if(!g_bH){document.<span class="exDesc">&rArr;</span> getElementById('droplist').style.display='block';}"&gt; &nbsp; 

Now, when the mouse hovers over the dropdown &lt;div&gt; either the browser will show the droplist &lt;ul&gt; 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.