Using setTimeout to Delay Showing Event-Delegation Tooltips

read 23 comments

In my last two tutorials, I explained how to use event delegation for showing and hiding tooltips. In a comment on the first one, Jan Aagaard asked how we might go about enhancing the script by adding a small delay before showing a tooltip. The answer lies with two JavaScript functions, setTimeout() and clearTimeout().

Setting Up

Picking up where we left off last time, I'm going to create the tooltip div, declare a couple variables, and then bind "mouseover," "mousemove," and "mouseout" all at once. In addition to $livetip and tipTitle, I'm declaring showTip (and leaving it undefined for the moment) and delay. The showTip variable will be used as a reference for the setTimeout function, and delay will be used to set the time after which the function within setTimeout's argument will execute (in this case, 300ms).

JavaScript:
  1. var $liveTip = $('<div id="livetip"></div>').hide().appendTo('body');
  2. var tipTitle = '',
  3.     showTip,
  4.     delay = 300;
  5.  
  6. $('#mytable').bind('mouseover mouseout mousemove', function(event) {
  7.   // ... code continues
  8. });

Of course, you don't have to use a variable for the delay, but I like to have that sort of setting up top where I can find it easily. Also, if I ever decide to convert this script into a plugin, having the delay stored in a variable will make it simpler to convert it to an option.

Setting the Timeout

Inside the .bind(), and after the business of determining whether the event is being triggered by a link or one of its descendants, the setTimeout is called on line 12:

JavaScript:
  1. var $liveTip = $('<div id="livetip"></div>').hide().appendTo('body');
  2. var tipTitle = '',
  3.     showTip,
  4.     delay = 300;
  5.  
  6. $('#mytable').bind('mouseover mouseout mousemove', function(event) {
  7.   var $link = $(event.target).closest('a');
  8.   if (!$link.length) { return; }
  9.   var link = $link[0];
  10.  
  11.   if (event.type == 'mouseover') {
  12.     showTip = window.setTimeout(function() {
  13.       $link.data('tipActive', true);
  14.       tipTitle = link.title;
  15.       link.title = '';
  16.       $liveTip
  17.         .html('<div>' + tipTitle + '</div><div>' + link.href + '</div>')
  18.         .show()
  19.         .css({
  20.           top: event.pageY + 12,
  21.           left: event.pageX + 12
  22.         });
  23.    
  24.     }, delay);
  25.   }
  26.   // ... code continues
  27. });

setTimeout is actually a method of the window object; as such, the this keyword inside its function argument will refer to window. It's not of much concern here because, with event delegation, we're relying on event.target rather than this, but I thought it might be worth mentioning anyway.

Note that within the setTimeout function, I'm storing a little data (line 13) for the link to indicate that the tooltip is active on it. This tipActive information then determines what to do on mouseout.

Clearing the Timeout

On mouseout, if the link has the tipActive data, that data is removed (line 29, below), the tooltip is hidden (line 30), and the link's original title attribute value is restored if tipTitle has a value (line 31). If that tipActive data is not associated with the link, then the timeout that was set on mouseover is reset with clearTimeout (line 33).

JavaScript:
  1. var $liveTip = $('<div id="livetip"></div>').hide().appendTo('body');
  2. var tipTitle = '',
  3.     showTip,
  4.     delay = 300;
  5.  
  6. $('#mytable').bind('mouseover mouseout mousemove', function(event) {
  7.   var $link = $(event.target).closest('a');
  8.   if (!$link.length) { return; }
  9.   var link = $link[0];
  10.  
  11.   if (event.type == 'mouseover') {
  12.     showTip = window.setTimeout(function() {
  13.       $link.data('tipActive', true);
  14.       tipTitle = link.title;
  15.       link.title = '';
  16.       $liveTip
  17.         .html('<div>' + tipTitle + '</div><div>' + link.href + '</div>')
  18.         .show()
  19.         .css({
  20.           top: event.pageY + 12,
  21.           left: event.pageX + 12
  22.         });
  23.    
  24.     }, delay);
  25.   }
  26.  
  27.   if (event.type == 'mouseout') {
  28.     if ($link.data('tipActive')) {
  29.       $link.removeData('tipActive');
  30.       $liveTip.hide();
  31.       link.title = tipTitle || link.title;        
  32.     } else {
  33.       window.clearTimeout(showTip);
  34.     }
  35.   }
  36.  
  37.   if (event.type == 'mousemove' && $link.data('tipActive')) {
  38.     $liveTip.css({
  39.       top: event.pageY + 12,
  40.       left: event.pageX + 12
  41.     });
  42.   }
  43. });

The mousemove part hasn't changed from what it was in the previous post, except that it first checks if the "tipActive" data is assigned to the link. I figured that setting the top and left properties of the tooltip is probably more work than doing a little check on the link's data, so why bother setting them if I don't have to?

So, that's it. Just add a simple setTimeout delay and conditionally clear it. If you want to set a delay on mouseout, you can do that the same way.

Check out the demo or download the zip (or neither, if you're stubborn).

Flipping the Tip

Next time, I'll show how to flip the tooltip from the right side of the cursor to the left if it would otherwise get cut off by the edge of the browser window.

comment feed

23 comments

  1. Gotta love tips like these -- they work really quite well. I love timeouts. :)

    A good followup could be to write code that adjusts for the tooltip possibly going off-screen (as it does with the last tip on the link to row 1 way down at the bottom), to ensure that event.pageX + the height of the tooltip doesn't exceed the bottom of the viewport, and adjusting the value set into top (and likewise, for left/pageY). :)

    • Absolutely, Brian. I actually mentioned that in the last sentence of this post: "Next time, I'll show how to flip the tooltip from the right side of the cursor to the left if it would otherwise get cut off by the edge of the browser window."

      Cheers.

  2. If you're looking for an easy-to-use, chainable jQuery plugin to help with delayed code execution, interval and timeout management, polling loops and debouncing.. and hover intent, check out my jQuery doTimeout plugin!

  3. Thank you very much. Not only for the usefulness of your scripts, but also (and more important) for the teaching
    The more of your articles I read, the more I learn to keep it simple

  4. Jeff

    First of all thanks for the great site and a really useful tutorial. I think to make the mousemove function a little more accurate you could remove the check for tipActive. It might have slightly higher cpu overhead this way when the tooltip is not shown but when the tooltip is shown it will be positioned more accurately. Also it would eliminate the need for the CSS method in your mouseover event. Let me know your thoughts, thanks again.

    • Hi Jeff,
      Yeah, I definitely see your point. I'll keep your suggestion in mind as I work on the next iteration. Thanks so much.

      • kuddl

        Hi Karl,
        GREAT follow up posts!!

        Still waiting for the next post!
        Would love to see how you target that issue of viewport positioning.
        Greez from Germany

  5. Rupesh

    is there any online editor to learn jquery like online javascripts editor.

  6. Matias

    Too bad the tooltips are not displayed when scrolling through the list using a scroll wheel with the pointer hovering over the links in Chrome or showing but with the wrong title in IE8.

    Seems to work as expected in Safari and Firefox though.

    Any idea how to fix that?

  7. Matias

    I should ad that if you move the pointer after scrolling with the wheel, the tooltip is displayed correctly in Chrome and IE8.

  8. Matias

    add, that is ;)

  9. nathan

    Eagerly awaiting the next iteration. It seems with a bit of logic added to improve tooltip placement this is going to be perfect.

    • Hi Nathan,
      Yeah, sorry about the delay. I have the script ready to go, but I just haven't found time to write up the blog entry to go along with it. Will try to do so soon.

  10. Is this a new feature in jQuery 1.4 ?

  11. Peter Nguyen

    I think John.Johnson means the new delegate() function in jQuery 1.4. Maybe you should write another followup that use that function too?

  12. Hi Karl,

    Just wondering what happens if you don't clear a Timer with clearTimeout()?

    For example I am using setTimeout() to delay execution of a function once in JS. If i subsequently forget to clear that Timer does it result in a memory leak?

    Also is there any way to check if any Timers are running?

    Hope i'm making sense?

  13. Thank you very much. Not only for the usefulness of your scripts, but also (and more important) for the teaching
    The more of your articles I read, the more I learn to keep it simple

  14. Great guide for a jQuery noob like myself.

    I am very new to JavaScript and jQuery (a couple of days new!) however i was wondering why you have use the setTimeout function instead of the jQuery delay() function.

    Also do you know of any way I could set the tooltip to continue to display IF the user puts the mouse within the actual tooltip.

    • You could use .delay(), but I don't think it would make things any simpler in this case. The .delay() method just uses a setTimeout to delay execution of the next item in a particular queue. It uses the "fx" queue by default, which allows us to do something very simple and clean with animations like $('div.somediv').fadeIn().delay(4000).fadeOut(), but in this case you'd need to use a custom queue name and then follow the delay with the .queue() method using the function as a callback. It gets a little tricky.

      To avoid closing the tooltip when the user puts the mouse within the tooltip, you'd first need to modify line 8 to something like:
      if (!$link.length && !$(event.target).closest('#live-tip').length ) { return; }
      Then, when you check for event.type == 'mouseout', you'd also need to make sure that event.relatedTarget isn't the link or the tooltip before closing the tooltip.

      Hope that helps to point you in the right direction.

  15. Tyler V.

    One problem here is that you are required to make a new global variable for each timeout if you want to be able to clear it. With the Delayed Events Plugin for jQuery the timeout is stored in jQuery's internal event object, and can be cleared simply by calling $.stopDelayed on that event. Check it out.

    • I have no objection to using a plugin like the one you mentioned or Ben Alman's doTimeout plugin, but in my example, you aren't making a new global variable each time. You're reusing the same one, showTip.

      Also, I shouldn't have been so lazy in the demo code, but there is absolutely no reason that this has to be a global variable. Just put the script inside $(document).ready(function() { /* code goes here */});.

      Or you could put it in an IIFE: (function($) { /* code goes here */ })(jQuery);

      Look ma, no global variables!

One Ping

Sorry, but comments for this entry are now closed.