Hi all.

I'm trying to add a slight delay to drop-down menu. Though I assume that entails a call to setTimeout() somehow, I've tried all I can think of (which isn't much...) and am clueless. Thank you for any tips!

<script type="text/javascript">
 var timeout    = 200;
var closetimer = 0;
var ddmenuitem = 0;

function jsddm_open()
{  jsddm_canceltimer();
  jsddm_close();
   ddmenuitem = $(this).find('ul').css('visibility', 'visible');}
   
function jsddm_close()
{  if(ddmenuitem) ddmenuitem.css('visibility', 'hidden');}

function jsddm_timer()
{  closetimer = window.setTimeout(jsddm_close, timeout);}

function jsddm_canceltimer()
{  if(closetimer)
   {  window.clearTimeout(closetimer);
      closetimer = null;}}

$(document).ready(function()
{  $('#jsddm > li').bind('mouseover', jsddm_open)
   $('#jsddm > li').bind('mouseout',  jsddm_timer)});

document.onclick = jsddm_close;
     
    </script>

Dani AI

Generated

A few focused notes that build on ’s report and ’s timer approach. HoverIntent (as discovered in the thread) is the most robust, low-effort fix for accidental opens, but the timer pattern works fine if implemented with the right event/context handling. The usual reason an “open after delay” variant fails is that passing the open function directly to setTimeout loses the hovered element’s context, so the handler can’t find the submenu to show.

Capture the hovered element when scheduling the open (closure) or bind the function to the element. Example (keeps handlers off globals and uses mouseenter/mouseleave to avoid bubbling problems):

var openDelay = 200, opentimer = null;

function openMenu(elem) {
  $(elem).find('ul').css('visibility','visible');
}

function startOpenTimer() {
  var el = this;                    // capture the element that triggered the event
  clearTimeout(opentimer);
  opentimer = setTimeout(function(){ openMenu(el); }, openDelay);
}

$('#jsddm > li')
  .on('mouseenter', startOpenTimer)
  .on('mouseleave', function(){ clearTimeout(opentimer); /* run close logic here */ });

Alternatives that preserve context: use $.proxy or Function.prototype.bind when setting the timeout:

opentimer = setTimeout($.proxy(jsddm_open, this), openDelay);
// or (modern browsers)
opentimer = setTimeout(jsddm_open.bind(this), openDelay);

Extra tips: prefer mouseenter/mouseleave over mouseover/mouseout to avoid flicker from child elements; cancel the open timer on leave; delegate handlers from the menu container if items are dynamic; and add keyboard support (focus/blur) plus aria-expanded toggling for accessibility. These adjustments complement ’s structure and give the same control over how “eager” the menu feels without repeating the posted plugin code.

Recommended Answers

All 9 Replies

I think it's working.

To find out, try a longer timeout. At 200 milliseconds it will be barely noticable.

Airshow

I think it's working.

To find out, try a longer timeout. At 200 milliseconds it will be barely noticable.

Airshow

Hi Airshow--That line controls how long the dropdown remains visible after mouseout. What I'm wondering is if there might be a way to delay its appearance in the first place--folks are complaining that the dropdowns get in the way when they move the mouse up just a bit too high.

Ryujin,

I'll see what I can do when I get a moment. Time is at a premium over the next few days with Christmas and everthing.

Airshow

Ryujin,

Following a similar patterns to open and close, you get something like this (untested):

$(document).ready(function()
{
    var openTimeout = 200;
    var closeTimeout = 400;//edit as required
    var closetimer = null;//by initialising to null, clearTimeout(closetimer) can be called without error before the timer has been set with setTimer(...).
    var opentimer = null;//as for closeTimer
    var ddmenuitem = null;

    var jsddm_open = function()
    {
        clearTimeout(closetimer);
        jsddm_close();
        ddmenuitem = $(this).find('ul').css('visibility', 'visible');
    }
    var vjsddm_open_timer = function()
    {
        clearTimeout(opentimer);
    	opentimer = setTimeout(jsddm_open, openTimeout);
    }

    var jsddm_close = function()
    {
        if(ddmenuitem) ddmenuitem.css('visibility', 'hidden');
    }

    var jsddm_close_timer = function()
    {
        closetimer = setTimeout(jsddm_close, closeTimeout);
    }

    $('#jsddm > li').bind('mouseover', jsddm_open_timer);
    $('#jsddm > li').bind('mouseout',  jsddm_close_timer);
    document.onclick = jsddm_close;
});

All vars and functions are established inside $(document).ready(...) to avoid globals, and some of the code is simplified.

jsddm_canceltimer was only called once and simplified to one line (now absorbed in jsddm_open), so it disappeared.

Airshow

Thank you Airshow -- I will fuss with this over the holiday and let you know of any progress (the new script fails to open the dropdowns at all, but I think i'm seeing the direction you point me toward). Enjoy the holiday--

Hello -- I am going to try something interesting I just stumbled across: a plugin called hoverIntent that seems to have been built with precisely this situation in mind. I'll let you know what happens--

Mmmm, "hoverIntent" ... sounds like a yogic camping experience. I would wait 'till the weather gets a bit wearmer.

Airshow

Ha! Unfortunate name aside, happily it turns out to be just what the doctor ordered, this hoverIntent.

It's not a simple delay, but something more sophisticated: it gauges mouse deceleration, so that if my cursor happens to brush up against/across the menu it does not drop down--not unless I linger there momentarily.

The sensitivity can be adjusted, too. Some have commented that it can make a menu seem "sluggish," but I don't see that...in testing our users said they were annoyed when the menu responded too readily.

Sounds like a good solution. I have bookmarked hoverIntent.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.