This is a simple tooltip plugin for jQuery. I wanted to create it in part to try out a few ideas, but also to have something a bit nicer than the default tooltips provided by the browsers. It doesn’t allow for ton of customization (none in fact), although the style can be easily modified via CSS. I like customization so it’s something I may add in the future but for now I wanted to get this going and not get bogged down in a more complex implementation. In the process of writing this latest iteration of the plugin (it’s not the first time I’ve started it) I had a bit of a “lightbulb” moment and I wanted to share it.
Mouseover is not the way to go
Don’t let schooling interfere with your education.
– Mark Twain
When you think about tooltips and, more specifically, how they are triggered, you probably think about on mouse hover/over/enter or any other variation of that. Indeed, that’s how I started this, but I didn’t want the tooltip to come up as soon as I hover over an element. It’s annoying. I only want the tooltip to show up when I pause over the element. So naturally, setTimeout(), right? Wrong!
setTimeout() delays the execution of the code, it does not delay the “logic”, which is what you kind of want to do here. Think about it. What setTimeout() does is say, “do this in 2 seconds”, and it will fire even if in 2 seconds the mouse is no longer over the element. What you want to actually do is “do this if the mouse is still over element in 2 seconds”. Now, you can still do that with setTimeout() but then you have to layer in the extra logic to basically clear the timer if the mouse out event fires up before the timer expires. Which is okay, and it works, but I couldn’t help but think that that’s a bit cloogy, there must be a better way to do it — a more elegant way.
The devil is in the details
So I decided to go back to the source as it were, and look again at the native behaviour of tooltips. I immediately noticed that the tooltip doesn’t fire simply on a delay but rather, when the mouse stops (over an element). There is still a delay but the key is — when the mouse stops. And it hit me — not mouse over, but mouse move, is the event I should be using. I still have to use setTimeout(), it’s just the nature of the beast (the beast being JavaScript), but I feel like this is a more elegant solution. So with that being said, here’s the entire code for the plugin, it’s really tiny — 70 lines with comments.
/*!
* Usage:
* $(selector).tooltip();
*/
(function($){
$.fn.tooltip = function(options){
var tipwrap = $("#tooltipWrap"),
tipcontent = "",
tipon = false,
tipheight = 0,
offset = { x:-13, y:-14 }, // offset the tip from mouse position
target = { x:0, y:0 };
return this.each(function(){
var $this = $(this),
title = $this[0].title,
timer;
// make sure the current element has a title, else move on to the next one
if (title === "") return true;
// clear the title attribute so the native tooltip doesn"t popup
$this.attr("title","");
$this.on("mousemove", function(e){
// this function is called if mouse has stopped moving (see below)
var onmousestop = function(){
// flag the tooltip as on
tipon = true;
// this is the tooltip container, we only need to create it the first time
// so we check if one already exists, if not we create it
if (!tipwrap.length) {
tipwrap = $("<div/>", {
id: "tooltipWrap",
class: "tooltip-wrap",
html: "<div class=\"tip-content\"></div><div class=\"tip-point tip-point-bottom\"></div>"
})
.hide().appendTo( $("body") );
// we also only need to get the handle for the content once
tipcontent = tipwrap.find(".tip-content");
}
// put the title into the tooltip content
// this is separate from the creation above because this needs to be done on every hover
tipcontent.text(title);
// we need the tooltip height (w/ content) to reposition it above the mouse pointer
tipheight = tipwrap.height();
// calculate the target coordinates (just makes the code neater)
target.x = e.pageX + offset.x;
target.y = e.pageY - tipheight + offset.y;
// position the element a bit off so we can slide it into view
tipwrap.css({ top:target.y - 15, left:target.x });
// slide and fade the tooltip into view
tipwrap.animate({ opacity:"show", top:target.y}, 250);
};
// run onmousestop after .4 seconds, UNLESS it is cleared by mouse move
clearTimeout(timer);
if (!tipon) timer = setTimeout(onmousestop, 400);
})
.on("mouseleave", function(e){
// we only need to hide the tooltip if it was actually on
if (tipon) tipwrap.animate({ opacity:"hide", top:target.y - 15}, 250);
// the flag and the time should always be reset to be sure
tipon = false;
clearTimeout(timer);
});
});
}; // end fn.tooltip
})(jQuery);
And this is how the styles for the tooltip look
.tooltip-wrap {
max-width:220px;
background:#333;
background:rgba(50,50,50,.9);
position:absolute;
border-radius:3px;
-webkit-box-shadow:rgba(0,0,0,.3) 1px 1px 2px;
-moz-box-shadow:rgba(0,0,0,.3) 1px 1px 2px;
box-shadow:rgba(0,0,0,.3) 1px 1px 2px;
}
.tooltip-wrap .tip-content {
color:#f9f9f9;
padding:6px 8px;
font-size:.9em;
}
.tooltip-wrap .tip-point {
width:0; height:0;
border:1px solid transparent;
position:absolute;
}
.tooltip-wrap .tip-point-bottom {
bottom:-8px; left:10px;
border-width:8px 5px 0 5px;
border-top-color:#333;
border-top-color:rgba(50,50,50,.9);
}
I added the plugin to this site, although right now it’s only applied to the social icons (top right), but it does let you see it in action. Ultimately I’d like to add some ways to customize it, like, should it appear above or below the mouse, make it more “intelligent” (right now it doesn’t re-position itself if it overflows the window) and add some formatting to the content inside.

Be the first to comment on this post