Bubble/Balloon Tooltips

... or how to make a nicer looking tooltip.

The default tooltips shown by browsers for the title attribute of an element can leave a bit to be desired.

Here I will present a technique to display custom tooltips in a little balloon or bubble. The application will require one image, a little bit of CSS, and some javascript. The image, shown on the right, is just a GIF file. Normally, I'd recommend a PNG, but, Internet Explorer has some issues with them.

example balloon image

Contents:

Everything that is not the bubble, should be transparent. Many thanks to James Taylor at TaylorMade Design for creating a much improved bubble image and sharing it with me. To make the image stretch and collapse, we will use a variation on the sliding doors technique. The image I am using has a width of 200 pixels and a height of 150 pixels. So, if you need to use a different size image, be sure to modify the CSS to match.

  1. abbr, acronym {
  2.   background-color: #e5e5e5;
  3.   padding-left: 0.2em;
  4.   padding-right: 0.2em;
  5.   border-bottom: dotted #000 1px;
  6. }
  7. .bubbleTooltip {
  8.   width: 200px;
  9.   color:#000;
  10.   font: 0.7em sans-serif;
  11.   text-decoration:none;
  12.   text-align:center;
  13. }
  14. .bubbleTooltip span.top {
  15.   padding: 30px 8px 0;
  16.   background: url(bubble.gif) no-repeat top;
  17. }
  18. .bubbleTooltip span.bottom {
  19.   padding:3px 8px 15px;
  20.   color: #000;
  21.   background: url(bubble.gif) no-repeat bottom;
  22. }

For this demonstartion, I am defining tooltips for the ABBR (Abreviation) and ACRONYM tags. The first part of the style sheet just adds grey background and little underline. This is just to provide a visual cue on the elements.

The bubbleTooltip defines the parent element for the tooltip bubble. The width should be the same width as the graphic used. The top and bottom nodes will be included inside of the bubbleTooltip element. They may both contain text but, I am only going to put text in the top one for this demo.

The Javascript

The javascript to make this work is broken up into three objects. The BubbleTips object is used to actually build the tooltips and bind the event handling. The ToolTipsEvents object contatins the events that the elements to receive the tooltips are bound to. The Effects object just provides little fade in and fade out effects. To activate the bubble tooltips on a type of object, we just need to tell the BubbleTips object to activate the tips. This is done with the function activateTipOn, it takes a tag name as a parameter. For example:

  1. window.onload = function(){
  2.   BubbleTips.activateTipOn("abbr");
  3.   BubbleTips.activateTipOn("acronym");
  4. };

This will activate the tooltips on both the ABBR and ACRONYM tags. Well, that covers how to use this project. Lets get to how it works. First up, the BubbleTips object.

BubbleTips

  1. var BubbleTips = {
  2.   opacity : "0.85",
  3.   bubbleNode : null,
  4.   activateTipOn : function(type){
  5.     var bubble = document.createElement("span");
  6.     bubble.style.position = "absolute";
  7.     bubble.style.zIndex = "9";
  8.     this.bubbleNode = bubble;
  9.     document.getElementsByTagName("body")[0].appendChild(bubble);
  10.     var tipTags = document.getElementsByTagName(type);
  11.     for(var i=0;i<tipTags.length;i++){
  12.       this.bindBubbleTip(tipTags[i]);
  13.     }
  14.   },
  15.   bindBubbleTip : function(elem) {
  16.     var tipText=elem.getAttribute("title");
  17.     if(tipText==null || tipText.length==0){
  18.       tipText="No title attribute, how sad :-(";
  19.     }
  20.     elem.removeAttribute("title");
  21.     var bubble = this.createElem("span","bubbleTooltip");
  22.     var tipTop = this.createElem("span","top");
  23.     tipTop.appendChild(document.createTextNode(tipText));
  24.     bubble.appendChild(tipTop);
  25.     bubble.appendChild(this.createElem("span","bottom"));
  26.     Effects.setOpacity(bubble,this.opacity);
  27.     elem.tooltip = bubble;
  28.     elem.onmouseover = ToolTipEvents.showTooltip;
  29.     elem.onmouseout = ToolTipEvents.hideTooltip;
  30.     elem.onmousemove = ToolTipEvents.followMouse;
  31.   },
  32.   createElem : function(tag,className){
  33.     var elem = document.createElement(tag);
  34.     elem.className = className;
  35.     elem.style.display = "block";
  36.     return elem;
  37.   }
  38. };

Well, we have seen how this gets started (activateTipOn), lets go through the implementation. The first thing in the object are a couple of properties. The opacity property specifies how opaque the tooltip balloon will be. The bubbleNode will be used to maintain a reference to the element containing the tooltip buble. In the function activateTipOn, it creates the node for the tooltip bubble (lines 89-91), add it to the document, and set the reference, described above, to point to it (line 92). The bubbleNode is then added to the page (line 93). Next, it searches through the document for all tags of the type specified (lines 94-97).

The bindBubbleTip function removes the title text from the tag, and adds it to the top span tag in the bubble (lines 100-108). It also adds a bottom node (to hold the bottom image) to the bubble (line 109). A rference to the new bubble nodes is added to the element (line 111), and finally, the events are bound to the node (lines 112-114).

The createElem function (lines 116-121) is just for convenience. It creates elements of the type in the tag attribute, with a CSS class of the className attribute. Its sets the block style, just to keep the form of the bubble.

ToolTipEvents

  1. var ToolTipEvents = {
  2.   offsetLeft : (-25),
  3.   offsetTop : (10),
  4.   posRef : function(){
  5.     return ((document.documentElement.scrollTop)?
  6.     document.documentElement : document.body)
  7.     },
  8.   showTooltip : function(e){
  9.     ToolTipEvents._cleanup();
  10.     BubbleTips.bubbleNode.appendChild(this.tooltip);
  11.     Effects.fadeIn(this.tooltip,BubbleTips.opacity);
  12.     ToolTipEvents.followMouse(e);
  13.   },
  14.   hideTooltip : function(e){
  15.     Effects.fadeOut(this.tooltip, ToolTipEvents._cleanup);
  16.   },
  17.   followMouse : function(e){
  18.     if(e == null){ e = window.event };
  19.     var posx = ToolTipEvents.offsetLeft;
  20.     var posy = ToolTipEvents.offsetTop;
  21.     if(e.pageX || e.pageY){
  22.       posx += e.pageX;
  23.       posy += e.pageY;
  24.     } else if(e.clientX || e.clientY) {
  25.       posx += e.clientX + posRef.scrollLeft;
  26.       posy += e.clientY + posRef.scrollTop;
  27.     }
  28.     BubbleTips.bubbleNode.style.top = (posy) + "px";
  29.     BubbleTips.bubbleNode.style.left = (posx) + "px";
  30.   },
  31.   _cleanup : function(){
  32.     var bubble = BubbleTips.bubbleNode;
  33.     if( bubble.childNodes.length > 0 ){
  34.       bubble.removeChild(bubble.firstChild);
  35.     }
  36.   }
  37. };

Much like the previous object, this one starts with a few properties. The offsetLeft and offsetTop properties determine where the bubble will be in relation to the mouse. The posRef function returns the a reference to help find the pointer based upon the user's browser. This will be used to help determine the location of the pointer when the event methods are called.

The function showTooltip is called on mouse over. This first cleans up any other events that may be in progress (like a fade out that hasn't finished). Then, it takes the bubble node associated with it earlier and adds it to the main bubbleNode object. Next, it starts the fade in going, and positions it relative to the cursor.

The function hideTooltip, called on mouse out, just calls the fade out (from the Effects) and passes a reference to the cleanup method (to be called when the fade out is finished).

The followMouse function is called when the mouse moves. It just finds the location of the pointer, and adjusts the position of the bubbleNode to be relative of it.

The cleanup method simply removes the node added during the showTooltip function.

Effects

  1. var Effects = {
  2.   fadeIn : function (elem,maxOpac){
  3.     elem.fadeIn = Effects._fadeIn;
  4.     elem.maxOpac = maxOpac;
  5.     elem.curOpac = 0;
  6.     this.cancelCurrent();
  7.     elem.fadeIn();
  8.   },
  9.   fadeOut : function (elem,fadeDoneF){
  10.     elem.fadeOut = Effects._fadeOut;
  11.     elem.fadeOutDone = fadeDoneF;
  12.     this.cancelCurrent();
  13.     elem.fadeOut();
  14.   },
  15.   cancelCurrent : function() {
  16.     clearTimeout(window.evtId);
  17.   },
  18.   _fadeIn : function() {
  19.     if( (+this.curOpac) < (+this.maxOpac) ){
  20.       this.curOpac = (+this.curOpac)+(0.05);
  21.       Effects.setOpacity(this,this.curOpac);
  22.       window.fadeInElem = this;
  23.       window.evtId = setTimeout(function(){
  24.         this.fadeInElem.fadeIn()
  25.         },30);
  26.       
  27.     } else {
  28.       Effects.setOpacity(this,this.maxOpac);
  29.       window.fadeInElem = null;
  30.     }
  31.   },
  32.   _fadeOut : function() {
  33.     if( (+this.curOpac) > 0 ){
  34.       this.curOpac = Math.max(0,(+this.curOpac)-(0.05));
  35.       Effects.setOpacity(this,this.curOpac);
  36.       window.fadeOutElem = this;
  37.       window.evtId = setTimeout(function(){
  38.         this.fadeOutElem.fadeOut()
  39.         },30);
  40.     } else if(this.fadeOutDone) {
  41.       this.fadeOutDone();
  42.       window.fadeOutElem = null;
  43.     }
  44.   },
  45.   setOpacity : function (elem,opac){
  46.     elem.style.filter="alpha(opacity:"+((+opac)*100)+")";
  47.     elem.style.KHTMLOpacity=opac;
  48.     elem.style.MozOpacity=opac;
  49.     elem.style.opacity=opac;
  50.   }
  51. };

The effects just provides fade in and fade out methods. These just delegate to recursive setTimeout calls to increase of decrease the opacity of the element to fade.

Resources

See it in Action

If you've found this useful enough to use on your site, send me a message with your URL and I'll feature you here.

Enjoy



Sponsors:

About willCode4Beer