Simple Tooltip for Huge Number of Elements

read 34 comments

There are many, many jQuery tooltip plugins out there, and some of them are very good. But when someone on the jQuery Google Group asked (a year ago) which plugin could handle displaying tooltips for 2,000 links on a page, I wasn't able to find one. So, I decided to throw together a quick little plugin myself and was surprised by how easy it was.

Event Delegation, Again

The key to having JavaScript handle hundreds, or even thousands, of elements on a page is to use event delegation. As Louis-Rémi Babé described in Working with Events, Part 3: More Event Delegation with jQuery, jQuery's .live() method makes event delegation dead easy. A simple tooltip script using .live() might look something like this:

JavaScript:
  1. $('<div id="livetip"></div>').hide().appendTo('body');
  2. var tipTitle = '';
  3.  
  4. $('a').live('mouseover', function(event) {
  5.   var $link = $(this);
  6.  
  7.   tipTitle = this.title;
  8.   this.title = '';
  9.   $('#livetip')
  10.   .css({
  11.     top: event.pageY + 12,
  12.     left: event.pageX + 12
  13.   })
  14.   .html('<div>' + tipTitle + '</div><div>' + this.href + '</div>')
  15.   .show();
  16. }).live('mouseout', function(event) {
  17.   this.title = tipTitle;
  18.   $('#livetip').hide();
  19. });

This script does the following:

  • Creates a single tooltip element that will be shown and hidden as the user mouses over links (line 1)
  • Hides the tooltip and appends it to the body (line 1)
  • Changes the tooltip's contents according to the moused-over link's title and href attributes (lines 7 and 14)
  • Places the tooltip on the page 12px below and to the right of the cursor position at the time that it entered the link (lines 9–13)
  • Shows the tooltip (line 15)
  • Sets the link's title attribute to an empty string when the user mouses over the link; this prevents the browser's default tooltip from appearing (line 8)
  • Sets the link's title back to what it was originally and hides the tooltip when the user mouses out of the link (lines 16–19)

A Little Style

At minimum, the tooltip needs to have the position: absolute style declaration for it to be positioned correctly on the page, but I threw in a little extra CSS to make it look more appealing:

CSS:
  1. #livetip {
  2.   position: absolute;
  3.   background-color: #cfc;
  4.   border: 2px solid #c9c;
  5.   border-radius: 4px;
  6.   -webkit-border-radius: 4px;
  7.   -moz-border-radius: 4px;
  8. }

Typically I like to include live demos directly in the blog entry, but since this one involves 2,000 links, I've set up separate demo pages. Check out the tooltip demo using .live(), or download the zip.

A Little Less Simple, A Little More Speedy

Using .live() in this way avoids binding event handlers directly to the thousands of links on the page. However, it doesn't prevent jQuery from searching the entire document for all of those links. After all, I'm still using the $('a') jQuery function. Also, at least for now, .live() binds events to document, forcing event bubbling all the way up the DOM each time. If there is a noticeable performance lag when the script is initially executed, it may help to use custom event delegation.

Update

As of jQuery 1.4.2, the .delegate() method can take the place of custom event delegation. As of jQuery 1.7, the .on() method can take the place of all other event methods, including .live() and .delegate().

Using custom event delegation, I bind the events to a containing table element:

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

With this approach, I needed to first make sure I was dealing with a link before I tried to do anything with it. Line 5 uses the .closest() method to select the <a> that is either the event.target or one of its ancestors. Line 6 checks the length property to see if any elements are included in the variable declared in line 5.

Note the use of .bind() instead of .live() and event.target instead of this. I could have used the .mouseover() and .mouseout() shortcut methods, but since I used .live() in the first example, it was easier to simply replace "live" with "bind." Also, you may be wondering why I used mouseover / mouseout when I could have used mouseenter / mouseleave or even the .hover() method (which uses mouseenter and mouseleave internally). The reason is that mouseenter and mouseleave prevent the event bubbling that this script relies on for its event delegation. Those two events would be triggered only when the mouse enters or leaves the full table, while mouseover and mouseout are triggered whenever the mouse enters or leaves any of the table's descendant elements, as well.

View the demo using custom event delegation, or download the zip.

One more Enhancement

Some people like having the tooltip move along with the cursor, as long as the mouse is over the element. Sometimes it's nice to be able move the tooltip a little if it initially overlaps something important. Adding the basic functionality is pretty straightforward. Just bind the mousemove event to the same table element:

JavaScript:
  1. $('<div id="livetip"></div>').hide().appendTo('body');
  2. var tipTitle = '';
  3.  
  4. $('#mytable').bind('mouseover', function(event) {
  5.   var $link = $(event.target).closest('a');
  6.   if ($link.length) {
  7.     var link = $link[0];
  8.  
  9.     tipTitle = link.title;
  10.     link.title = '';
  11.     $('#livetip')
  12.     .css({
  13.       top: event.pageY + 12,
  14.       left: event.pageX + 12
  15.     })
  16.     .html('<div>' + tipTitle + '</div><div>' + link.href + '</div>')
  17.     .show();
  18.   }
  19. }).bind('mouseout', function(event) {
  20.   var $link = $(event.target).closest('a');
  21.   if ($link.length) {
  22.     $link.attr('title', tipTitle);
  23.     $('#livetip').hide();
  24.   }
  25. }).bind('mousemove', function(event) {
  26.   if ($(event.target).closest('a').length) {
  27.     $('#livetip').css({
  28.       top: event.pageY + 12,
  29.       left: event.pageX + 12
  30.     });
  31.   }
  32. });

With the addition of lines 25 through 32, as the mouse moves over the links, the tooltip's top and left style properties are continually updated based on the mouse's position.

Try the demo and watch the tooltip move along with the mouse position, or download the zip.

The Plugin

The plugin doesn't offer much more than the scripts in this post, but feel free to try it out. One difference is that it doesn't rely on the .closest() method, so it can be used with jQuery 1.2.6 if you're stuck with that version for some reason. The syntax is a little funky:

JavaScript:
  1. $(document).ready(function() {
  2.   $(containerElement).eztip(invokingElement, [optionsMap]);
  3. });

The containerElement selector provides context so you can specify where exactly the event handlers are bound (unlike .live(), which binds to the document). The first argument of .eztip() is required. It should be a selector that matches the elements for which you wish to show a tooltip. The second argument is the typical options object, allowing for a handful of objects that you might expect to find in a stripped down tooltip plugin.

Let's Make Better Mistakes Tomorrow

The tooltip scripts in this post work fine, but they repeat code where they really shouldn't. Stay tuned tomorrow for a brief tutorial on one way to avoid such code repetition.

comment feed

34 comments

  1. Mahdi

    Great Tutorial , thanks Karl , I had the same experience with all the Tooltip plugins in the internet , non of them supported Event Delegation , good to know where to se the Closest() ,
    Best Tooltip Tutorial ever .

  2. Thanks for this post! I've been trying to optimize our collection list page that sometimes displays thousands of mouseovers and popups. Your explanation and examples will help me a lot!

  3. Jan Aagaard

    How would you add a small delay before showing the tooltip, so that you would have to hold the mouse still for e.g. 200 ms before the tip shows? I think there's a good usability reason that this is the default behavior when you add a title attribute to an element.

  4. violinista

    @Jan Aagaard:

    You can use jQuery hoverIntent plugin for this.

    Cheers

  5. Thanks Karl, Nice Work!

    Just wanted to let you know the link to your demo.zip file is not working. :)

    Thanks,
    AG

  6. You are voted!
    Track back from WebDevVote.com

  7. Hi,
    I read about the problems with huge amounts of targets for live events and you solved this quite well, I believe.

    However, there is one slight problem (at least in the demo, tested with opera and ff):
    If you move the mouse over a link while the page is loading, the normal title is displayed since the plugin is not yet loaded¹. Somehow this leads to the $link.title attribute to be emtpy as soon as you mouseout and mouseover again.

    Is there any good way we could prevent this from happening on javascript site?

    Best regards,
    Moritz Peters

    ¹ This is somewhat tricky. The mouse simply resting over a link in a loading page does not activate the browsers tooltip. The mouse must be moved somewhere on the link to activate the browsers tooltip. As soon as it's activated and the mouse does not leave the link until the plugin is loaded, this bug occurs. This means, the actual tooltip may never be displayed and the bug still occurs if you constantly move the mouse on the link and only mouseout after the plugin loaded.

  8. Thanks Karl, this is of such a great help. The best part was the tooltip moves along with the mouse position. This is the small yet the most important part when having tooltips for so many closely placed elements.
    This is one of the best tooltip solutions out there.

  9. robby biggonsworth

    How would you adjust the tooltips so they don't run off the right side of the screen? (which they currently do)

    Waiting for part 2!

    • Hi Robby,
      That is certainly a concern, but I didn't address it here because it wasn't relevant to the post's thesis (use event delegation to handle huge number of elements) and I wanted to keep true to the title (Simple Tooltip...). That said, I added width detection to another revision of the script and hope to write about that soon. Stay tuned.

  10. Ruana

    Hi,
    I'm an absolute beginner to JQuery and I don't understand how this works:
    The id #tooltip is written down in the CSS and the script but not in an element in the markup. But in the script it relates to a div container and not a table. How does this work without somehow referring to the specific element?

    Sorry about my english. I hope you understand my questions meaning.

  11. arnold

    Nice tutorial , Ive been waiting some tutorials here, finally its here, hope you post more. Thanks!
    Will use it in my project

  12. Nice tutorial, but I have one problem with it. If your link is close to the right or bottom edge of the screen, the tooltip will extend off screen and become completely or partially hidden.

  13. Matt

    Correct; and in Firefox, the tooltip makes the page "jump" if I scroll near the bottom.

    • Hi Matt,
      I'm aware of that problem in Firefox, but I don't know what the solution is, nor whether this is a Firefox bug or a jQuery bug. If anyone can shed any light on this problem, please do!

      • Matt

        I read a forum post about forcing the vertical scrollbar in Firefox by using, for example,
        html { min-height: 102%; }
        This stops the "jump" in Firefox, and IE8 and IE6 didn't appear to show any ill-effects.

        However, this doesn't seem to be the best of solutions, because I'm still coming across a horizontal scrollbar in any browser when the tooltip is at the far right of the page; also, it would be best if the tooltip could stay in view (shift its vertical coordinates) as it nears the bottom of a page, instead of getting cut-off from view. Could the javascript be modified to test for the bottom of the page and then shift its y-coordinate accordingly? Likewise, test for the far right of the page and then shift its x-coordinate accordingly? (Maybe there's a better way to do this; just thinking out loud.) Thanks.

  14. Who cares for the top most terrorizing (users and the Web) (pseudo-) browser… ???

  15. Great post! I noticed that there's a typo:

    "Those two events would be triggered only when the mouse enters or leaves the full table, while mouseover and mouseleave are triggered whenever the mouse enters or leaves any of the table's descendant elements, as well."

    I believe that the "mouseleave" should read "mouseout". Had me confused for a minute. I do know how easy it is to make such mistakes, having written well over a hundred CSS articles myself, oy...

  16. I really like this tooltip created using jQuery.

    Check out my blog at http://timothy-mcclimon.appspot.com for more jQuery examples.

  17. hello.....

    guys anybody please help me how can we install jquery in joomla.....

  18. Mike

    Great work - but the tooltip disappears when you reach the edge of the current window (e.g. when hovering over the last visible element in your demo list) - how about an update to allow for repositioning in such circumstances?

  19. Randy

    .live() is deprecated, with jQuery 1.7+ the .on() method should be used instead. Happy Holidays!

12 Pings

  1. [...] Simple Tooltip for Huge Number of Elements » Learning jQuery – Tips, Techniques, Tutoria... – [...]

  2. [...] 15. Simple Tooltip for Huge Number of Elements [...]

  3. [...] my last two tutorials, I explained how to use event delegation for showing and hiding tooltips. In a [...]

  4. [...] my last two tutorials, I explained how to use event delegation for showing and hiding tooltips. In a [...]

  5. [...] is resetting link.title to tipTitle only if tipTitle has a value. This, I’m hoping, fixes the problem reported by Moritz Peters of the tooltip not displaying correctly if you first mouse over a link before the document is [...]

  6. [...] A commonly overlooked advantage to event delegation is the ability to bind one event handler to any number elements. The handler is bound to the delegate rather than to each event target. Karl Swedberg does a great job of demonstrating this with his Tooltip example - (ex. Simple Tooltip for Huge Number of Elements) [...]

  7. [...] Simple Tooltip for Huge Number of Elements [...]

  8. [...] you how easily you can have automated quotation marks using <blockquote> tag in your webpages.Simple Tooltip for Huge Number of ElementsThere are many, many jQuery tooltip plugins out there, and some of them are very good. But when [...]

Sorry, but comments for this entry are now closed.