Click here to Skip to main content
14,984,032 members
Articles / Web Development
Article
Posted 12 Sep 2013

Stats

43.3K views
584 downloads
24 bookmarked

Creating JavaScript Tooltips Has Never Been This Easy

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
12 Sep 2013MIT6 min read
This article demonstrates how to create JavaScript tooltips.
In this article, 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.

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.

Image 1

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.

Let's 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 scratch.

HTML
<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> 

Image 2

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 let's 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.

JavaScript
<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>  

Image 3

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.

HTML
<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> 

Let's 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 let's 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.

HTML
<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> 

Image 4

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, it's purely up to you guys to add the features that you like. So let's 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:

JavaScript
;
(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:

JavaScript
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:

JavaScript
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 are its parameters and their description:

JavaScript
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:

JavaScript
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 many more scenarios. If you find any issues or bugs, then feel free to provide them here or add them on GitHub.

History

  • 13th September, 2013: Initial version

License

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

Share

About the Author

Nitij
Software Developer (Senior)
India India
Just a regular guy interesting in programming, gaming and a lot of other stuff Smile | :)

Please take a moment to visit my YouTube Channel and subscribe to it if you like its contents!
My YouTube Channel

Don't be a stranger! Say Hi!!

Cheers!

Comments and Discussions

 
GeneralMy +5 Pin
Raje_12-Sep-13 23:15
MemberRaje_12-Sep-13 23:15 
GeneralRe: My +5 Pin
Nitij13-Sep-13 1:08
professionalNitij13-Sep-13 1:08 

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

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