Firefox/Mozilla flicker-free drop-down
2005-05-20 01:00 by Peter Asquith —
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.⇒
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.⇒
getElementById('droplist').style.display='none';}"
onmouseover="if(!g_bH){document.⇒
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.
Commenting is closed for this article.
Copyright © 1997-2008, Peter S Asquith. Most rights reserved.