Click here to Skip to main content
Click here to Skip to main content

Creating Javascript Tooltips Has Never Been This Easy

By , 12 Sep 2013
Rate this:
Please Sign up or sign in to vote.

Introduction  

Tooltips constitute a very important part in the UI of the websites. They provide a very simple and elegant way to provide additional information about the webpage elements. But normally when we have such a requirement to implement tooptips for webpages, we either begin from scratch or we use pre-made javascript tooltip libraries that are available out there. This article is not about pointing out which approach is better, here I will demonstrate how to create simple tooltips and then evolve them into more complex, feature rich cool tooltips that we see out there on the web.


Using the code   

Tooltips are simply html controls that we show and hide on mouse-over and mouse-out events of html elements. I prefer using a div for that purpose and it is purely a matter of your choice. I have used jQuery wherever I can to reduce the amount of code and to increase cross browser compatibility.

Lets start with the very basic implementation of javascript tooltip, we will improve this code until we reach a point when we can move to the final implementation. 

In the following code snippet, a very simple tooltip is being applied to a shopping cart item. This tooltip code is not even acceptable by any standard but we are coding from the scratch.  

<body>
        <div id= "Div1" style="width:220px;height:250px;">
            <img src="images/db-apparel_The-Swede_white_large.jpg" />            
        </div>
        <div id="divToolTip" style="border:1px solid black;padding:5px;display:none;width:80px;height:50px;">
                The Swede White, $29
        </div>
        
        
        <script type="text/javascript">
            //called after window content is loaded.
            function contentLoaded() {
                //mouse-over event handler
                $("#Div1").mouseover(function () {
                    $("#divToolTip").css("display", "inline");
                });
 
                //mouse-out event handler
                $("#Div1").mouseout(function () {
                    $("#divToolTip").css("display", "none");
                });
            }
            
            window.addEventListener("DOMContentLoaded", contentLoaded, false);
        </script>
    </body> 

The above code is very easy to implement. We have functions bound to the mouse-over and mouse-out events of our t-shirt div, on mouse over we are showing the tooltip div and on mouse out we are hiding it. This code is as easy as it can get to implement our tooltips but the output has some serious drawbacks: 

  1. Lack of proper positioning of the tooltip.
  2. Static Code.
  3. Need to manually add a div for the tooltip. 
So lets deal with the first problem, that is to properly position our tooltip. The following code positions the tooltip at the top right of the source element.
<body>
        <div id= "Div1" style="width:220px;height:250px;">
            <img src="images/db-apparel_The-Swede_white_large.jpg" />            
        </div>
        <div id="divToolTip" style="border:1px solid black;padding:5px;display:none;width:80px;height:50px;position:absolute">
                The Swede White, $29
        </div>
        
        
        <script type="text/javascript">
            //called after window content is loaded.
            function contentLoaded() {
                //mouse-over event handler
                $("#Div1").mouseover(function () {
                    var divToolTip = $("#divToolTip")
                    var sourceControl = $("#" + this.id);
                    var top = sourceControl.offset().top;
                    var right = sourceControl.offset().left + sourceControl.outerWidth();
                    divToolTip.css("top", top);
                    divToolTip.css("left", right);
                    divToolTip.css("display", "inline");
                });
 
                //mouse-out event handler
                $("#Div1").mouseout(function () {
                    $("#divToolTip").css("display", "none");
                });
            }
            
            window.addEventListener("DOMContentLoaded", contentLoaded, false);
        </script>
    </body>  

In the above code, jQuery offset() method gets the current coordinates of the element relative to the document. So we are simply retrieving the left and right coordinates of the source element and then we are placing our div at those exact coordinates(remember to set the value of css:position = 'absolute' for the tooltip div).

Let us now visit the problems here in this code: 

  1. Static code 
  2. Static position.
  3. Need to manually add a div for the tooltip. 
The next code snippet eliminates the 1st and 3rd problem. Some of you may not even consider them as problems for small websites but this kind of code becomes very hard to manage when we scale the application.
<body>
        <div id= "Div1" style="width:220px;height:250px;">
            <img src="images/db-apparel_Canonflex_large.jpg" />            
        </div>
        
        <script type="text/javascript">
            function ApplyToolTip(sourceControlId, content) {
                var divToolTip = null;
                var sourceControl = $("#" + sourceControlId);
                divToolTip = $("#divToolTip");
                if (!(divToolTip.length > 0)) {
                    divToolTip = document.createElement("div");
                    divToolTip.setAttribute("id", "divToolTip");
                    $("body").append(divToolTip);
                    divToolTip = $("#divToolTip");
                    divToolTip.css("position", "absolute");
                    divToolTip.css("display", "none");
                }
 
                divToolTip.css("border", "1px solid black");
                divToolTip.css("padding", "5px");
 
                //mouse-over event handler
                $("#" + sourceControlId).mouseover(function () {
                    var top = sourceControl.offset().top;
                    var right = sourceControl.offset().left + sourceControl.outerWidth();
                    divToolTip.css("top", top);
                    divToolTip.css("left", right);
                    divToolTip.html(content);
                    divToolTip.css("display", "inline");
                });
 
                //mouse-out event handler
                $("#" + sourceControlId).mouseout(function () {
                    $("#divToolTip").css("display", "none");
                });
            }
 
            //called after window content is loaded.
            function contentLoaded() {
                ApplyToolTip("Div1", "1959, $29");
            }
            
            window.addEventListener("DOMContentLoaded", contentLoaded, false);
        </script>
    </body> 

Lets see what is happening in the above code:

  • We are creating a div on the fly whenever we apply the tooltip to any page element. This div will then be used every time we want to show the tooltip for any page element.
  • The mouse-over and mouse-out functions are being set from ApplyToolTip function. This gives us the liberty to set tooltips by just calling a function and passing all the required parameters. 
Problem that still exist is:
  1. Static position. 

Our tooltip code has a drawback that it is always showing at the top right position. Now lets suppose our source element is right aligned and there is not much space left to properly accommodate the tooltip, then with the current code the tooltip will cross the boundary of the browser window and as a result will get clipped.

To sort this out we have to conditionally check if there is enough space for our tooltip to show between the source element and the window boundary. 

<body>
        <div id= "Div1" style="width:220px;height:250px;">
            <img class= "productImage" src="images/db-apparel_Canonflex_large.jpg" />            
        </div>
        <br /><br /><br /><br />
        <div id= "Div2" style="width:220px;height:250px;">
            <img src="images/db-apparel_Pentax6x7-seafoam_large.jpg" />            
        </div>        
        
        <script type="text/javascript">
            function ApplyToolTip(sourceControlId, content) {
                var divToolTip = null;
                var sourceControl = $("#" + sourceControlId);
                divToolTip = $("#divToolTip");
                if (!(divToolTip.length > 0)) {
                    divToolTip = document.createElement("div");
                    divToolTip.setAttribute("id", "divToolTip");
                    $("body").append(divToolTip);
                    divToolTip = $("#divToolTip");
                    divToolTip.css("position", "absolute");
                    divToolTip.css("display", "none");
                }
 
                divToolTip.css("border", "1px solid black");
                divToolTip.css("padding", "5px");
 
                //mouse-over event handler
                $("#" + sourceControlId).mouseover(function () {
                    var targetLeft = null, targetTop = null; //top and left of the tooltip div
                    var top = sourceControl.offset().top;
                    var left = sourceControl.offset().left;
                    var right = sourceControl.offset().left + sourceControl.outerWidth();
                    var bottom = sourceControl.offset().top + sourceControl.outerHeight();
                    divToolTip.html(content);
 
                    //check for suitable location to show the tooltip
                    //sequence is top>right>left>bottom
                    if (!(divToolTip.outerHeight() > top)) { //top
                        targetLeft = left;
                        //we need to set css left here to correctly compute the tooltip div height
                        divToolTip.css("left", targetLeft);
                        targetTop = top - divToolTip.outerHeight();
                    }
                    else if (!(divToolTip.outerWidth() > $(document).width() - right)) { //right
                        targetLeft = right;
                        targetTop = top;
                    }
                    else if (!(divToolTip.outerWidth() > left)) { //left
                        targetLeft = left - divToolTip.outerWidth();
                        targetTop = top;
                    }
                    else if (!(divToolTip.outerHeight() > $(document).height() - bottom)) { //bottom
                        targetLeft = left;
                        targetTop = bottom;
                    }
 
                    divToolTip.css("top", targetTop);
                    divToolTip.css("left", targetLeft);
                    divToolTip.css("display", "block");
                });
 
                //mouse-out event handler
                $("#" + sourceControlId).mouseout(function () {
                    $("#divToolTip").css("display", "none");
                });
            }
 
            //called after window content is loaded.
            function contentLoaded() {
                ApplyToolTip("Div1", "1959, $29");
                ApplyToolTip("Div2", "Six By Seven, $29");
            }
            
            window.addEventListener("DOMContentLoaded", contentLoaded, false);
        </script>
    </body> 

In the above code, we have implemented a sequence to show the tooltips according to the space availability. So we won't have to worry about the tootip getting clipped at the window boundary.

Our tooltip code is now fairly dynamic, but it is contained in a script tag so if we want to use this code for multiple web pages then we will have to copy this code everywhere and that means a lot of code redundancy. The solution for this is to create a separate module that can be used anywhere globally. To do this we simply have to put our code in a closure and expose a single object for global use.

This is the point where we should move to the final version instead of viewing more code snippets because we are done with implementing the basic stuff. After this its purely up to you guys to add the features that you like. So lets just have the final version that I have implemented after adding some more features. 

The following code needs to be in a separate javascript file:  

;
(function (jQuery, w) {
    var $ = jQuery;
    var toolTipJS = function () {
        //***Summary***
        //array to hold tooltip location preferences
        //*************
        this.locationPreference = [];

        //***Summary***
        //Location object to be added to the location preference list
        //*************
        this.tooltipLocation = function (location, className) {
            this.location = location;
            this.className = className;
        };

        //***Summary***
        //tooltip location constants
        //*************
        this.LocationConstants = {
            Top: 1,
            Left: 2,
            Right: 3,
            Bottom: 4
        };

        //***Summary***
        //Add a location preference
        //*************
        this.addLocationPreference = function (l) {
            this.locationPreference.push(l);
        };

        //***Summary***
        //Resets location preferences
        //*************
        this.resetLocationPreference = function() {
            this.locationPreference = [];
        }

        //***Summary***
        //Flag to check if the mouse pointer is inside the source element
        //*************
        this.inside = false;

        //***Summary***
        //applies the tooltip show and hide functions on the mouseover and
        //mouseout events of the source control
        //***Params****
        //sourceControlId = ID of source control.
        //content = Tooltip content.
        //distance = Distance between the tooltip and the source control.
        //showAtPointer = Flag to determine if the tooltip will e stationary
        //or will be moving with the mouse pointer
        //*************
        this.applyTooltip = function (sourceControlId, content, distance, showAtPointer) {
            var divToolTip = null;
            var showTooltipDelegate = null;
            var hideTooltipDelegate = null;
            var sourceControl = $("#" + sourceControlId);
            var params = null;
            divToolTip = $("#divToolTip");

            //create our tooltip div if not already present
            if (!(divToolTip.length > 0)) {
                divToolTip = document.createElement("div");
                divToolTip.setAttribute("id", "divToolTip");
                $("body").append(divToolTip);
                divToolTip = $("#divToolTip");
                divToolTip.css("position", "absolute");
                divToolTip.css("display", "none");
            }

            //delegate to change the calling context to our toolTipJS object
            showTooltipDelegate = $.proxy(showToolTip, this);
            hideTooltipDelegate = $.proxy(hideTooltip, this);
            params = {
                "sourceControl": sourceControl,
                "content": content,
                "distance": distance,
                "showAtPointer": showAtPointer
            }

            if (showAtPointer === false) {
                sourceControl.mouseover(params, showTooltipDelegate);
            }
            else {
                sourceControl.mousemove(params, showTooltipDelegate);
            }            

            sourceControl.mouseout(hideTooltipDelegate);
        };
    };

    //***Summary***
    //show the tooltip after computing the position and the correct style to apply on
    //the tooltip div.
    //*************
    function showToolTip(e) {
        var i = 0;
        var showAtPointer = e.data.showAtPointer;
        var sourceControl = e.data.sourceControl;
        var content = e.data.content;
        var targetLeft = null, targetTop = null; //top and left of the tooltip div
        var top = sourceControl.offset().top;
        var left = sourceControl.offset().left;
        var right = sourceControl.offset().left + sourceControl.outerWidth();
        var bottom = sourceControl.offset().top + sourceControl.outerHeight();        
        var divToolTip = $("#divToolTip");
        var distance = e.data.distance;

        if (showAtPointer === true) {
            left = right = e.pageX;
            top = bottom = e.pageY;
        }

        divToolTip.removeClass(); //remove any previous class
        //reset top and left
        if (this.inside === false) {
            divToolTip.css("top", 0);
            divToolTip.css("left", 0);
        }
        divToolTip.html(content); //set the tooltip content
        for (; i < this.locationPreference.length; i++) {
            switch (this.locationPreference[i].location) {
                case this.LocationConstants.Top:
                    if (divToolTip.outerHeight() + distance > top) {
                        continue;
                    }
                    else {
                        //need to set the css here so as to retrieve final height after applying css
                        divToolTip.addClass(this.locationPreference[i].className);
                        targetLeft = left;
                        //we need to set css left here to correctly compute the tooltip div height
                        divToolTip.css("left", targetLeft);
                        targetTop = top - divToolTip.outerHeight() - distance;
                    }
                    break;
                case this.LocationConstants.Right:
                    if ((divToolTip.outerWidth() + distance) > ($(window).width() - right)) {
                        continue;
                    }
                    else {
                        divToolTip.addClass(this.locationPreference[i].className);
                        targetLeft = right + distance;
                        targetTop = top;
                    }
                    break;
                case this.LocationConstants.Left:
                    if (divToolTip.outerWidth() + distance > left) {
                        continue;
                    }
                    else {
                        //need to set the css here so as to retrieve final width after applying css
                        divToolTip.addClass(this.locationPreference[i].className);
                        targetLeft = left - divToolTip.outerWidth() - distance;
                        targetTop = top;
                    }
                    break;
                case this.LocationConstants.Bottom:
                    if (divToolTip.outerHeight() + distance > $(window).height() - bottom) {
                        continue;
                    }
                    else {
                        divToolTip.addClass(this.locationPreference[i].className);
                        targetLeft = left;
                        targetTop = bottom + distance;
                    }
                    break;
            }
            
            break;
        }
        //apply the top and left for the tooltip div
        divToolTip.css("top", targetTop);
        divToolTip.css("left", targetLeft);
        if (this.inside === false) {
            divToolTip.css("display", "block");
            this.inside = true;
        }        
    };

    //***Summary***
    //hides the toooltip div.
    //*************
    function hideTooltip() {
        this.inside = false;
        $("#divToolTip").css("display", "none");
    };
    
    w["ToolTipJS"] = toolTipJS;
})($, window); 

The above code can be used by including the javascript file in the webpage. There are some more additions to this code which are as follows: 

  1. Creation of a single object which is exposed to the window and then can be accessed globally to set the tooltips for different page elements.
  2. Addition of a 'distance' parameter if we have to put some gap between the source element and the tooltip div. 
  3. Addition of a location preference list locationPreference so that our tooltips don't have to always use the same sequence of locations to show on the page. 
  4. Addition of a variant  in which tooltip moves with the mouse move over the element instead of showing at a static position. 

This code can be easily modified to add more custom and dynamic tooltip positions around the source element. Following is the description about using this code, additional documentation can be found on the Github page of this code.  

First we need to add the location preferences for our tooltip, to do this you need to call the TooltipJS.addLocationPreference function and it is advisable to add all the available locations in your preferred sequence. Its parameters are these:

addLocationPreference(location) 

  • Location: An object of type TooltipJS.tooltipLocation which contains the location constant and the css class name.   

TooltipJS.tooltipLocation object constructor has the following parameters: 

  • location: A value from the TooltipJS.LocationConstants indicating a single location.
  • className: Css class to be applied when the tooltip is showing at that particular location.
Here is its usage:
 tooltipJS.addLocationPreference(new tooltipJS.tooltipLocation(tooltipJS.LocationConstants.Top, "tooltip-Top"));  

After that we can apply the tootip to any webpage element, to do that we need to call ToolTipJS.applyTooltip function. Here is its parameters and their description:

ToolTipJS.applyTooltip(sourceControlId, content, distance, showAtPointer)  

sourceControlId: Id of the source element for which we want to apply the tooltip.

content: Tooltip content, this can be string or any valid html text. 

distance: Distance between the source element and the tooltip. If showAtPointer is true then this the distance between the current mouse position and the tooltip. 

showAtPointer: If set to true then the tooltip will move with the moving mouse pointer over the source element.  

Here is its usage:

tooltipJS.applyTooltip("Div1", getProductContent("The Swede White", "29.00", "white"), 20, false);
tooltipJS.applyTooltip("Div2", getProductContent("Six by Seven", "29.00", "#DAF4F0"), 20, true); 

Here getProductContent is a function that I made to fill data into a template, it is not part of the tooltip object. 

You can find additional function reference on the GitHub page. 

I generated the speech bubble css from here so you can apply some cool and interesting css apart from the ones in the sample provided. 

The cool t-shirt images are from here in case you were thinking of buying one Wink | <img src=.

Browser Support

Chrome: Great..!!

Firefox: Awesome..!!

Internet Explorer: ??? 

Conclusion 

I created this library for very specific needs, but I am sure that it can be used for much more scenarios. If you find any issue or bug then feel free to provide them here or add them at GitHub

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

Nitij
Software Developer
India India
Just a regular guy writing regular code Smile | :)
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralMy +5 PinprofessionalRaje_12-Sep-13 23:15 
GeneralRe: My +5 PinprofessionalNitij Kumar13-Sep-13 1:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 13 Sep 2013
Article Copyright 2013 by Nitij
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid