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.
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.
<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">
function contentLoaded() {
$("#Div1").mouseover(function () {
$("#divToolTip").css("display", "inline");
});
$("#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:
- Lack of proper positioning of the tooltip
- Static code
- 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.
<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">
function contentLoaded() {
$("#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");
});
$("#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:
- Static code
- Static position
- 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");
$("#" + 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");
});
$("#" + sourceControlId).mouseout(function () {
$("#divToolTip").css("display", "none");
});
}
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:
- 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.
<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");
$("#" + sourceControlId).mouseover(function () {
var targetLeft = null, targetTop = null;
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);
if (!(divToolTip.outerHeight() > top)) {
targetLeft = left;
divToolTip.css("left", targetLeft);
targetTop = top - divToolTip.outerHeight();
}
else if (!(divToolTip.outerWidth() > $(document).width() - right))
{
targetLeft = right;
targetTop = top;
}
else if (!(divToolTip.outerWidth() > left)) {
targetLeft = left - divToolTip.outerWidth();
targetTop = top;
}
else if (!(divToolTip.outerHeight() > $(document).height() - bottom))
{
targetLeft = left;
targetTop = bottom;
}
divToolTip.css("top", targetTop);
divToolTip.css("left", targetLeft);
divToolTip.css("display", "block");
});
$("#" + sourceControlId).mouseout(function () {
$("#divToolTip").css("display", "none");
});
}
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, 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:
;
(function (jQuery, w) {
var $ = jQuery;
var toolTipJS = function () {
this.locationPreference = [];
this.tooltipLocation = function (location, className) {
this.location = location;
this.className = className;
};
this.LocationConstants = {
Top: 1,
Left: 2,
Right: 3,
Bottom: 4
};
this.addLocationPreference = function (l) {
this.locationPreference.push(l);
};
this.resetLocationPreference = function() {
this.locationPreference = [];
}
this.inside = false;
this.applyTooltip = function (sourceControlId, content, distance, showAtPointer) {
var divToolTip = null;
var showTooltipDelegate = null;
var hideTooltipDelegate = null;
var sourceControl = $("#" + sourceControlId);
var params = null;
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");
}
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);
};
};
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;
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();
if (this.inside === false) {
divToolTip.css("top", 0);
divToolTip.css("left", 0);
}
divToolTip.html(content);
for (; i < this.locationPreference.length; i++) {
switch (this.locationPreference[i].location) {
case this.LocationConstants.Top:
if (divToolTip.outerHeight() + distance > top) {
continue;
}
else {
divToolTip.addClass(this.locationPreference[i].className);
targetLeft = left;
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 {
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;
}
divToolTip.css("top", targetTop);
divToolTip.css("left", targetLeft);
if (this.inside === false) {
divToolTip.css("display", "block");
this.inside = true;
}
};
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:
- 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.
- Addition of a '
distance
' parameter if we have to put some gap between the source element and the tooltip div. - 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. - 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 are 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 .
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