Tooltips are the best non-mobile way to describe something very interactively. The User hovers or clicks on an Element, and a small popover shows additional informations to it. That’s wonderful and perfect, but how can you write your own Tooltips library? Of course, you can use a small script like tippy.js, which depends on the not-so-small Popper.JS library. But why should you? A own Tooltips JavaScript is less then 80 lines of code!

HowTo Code Tooltips (JS Part)

The most Tooltip libraries and scripts works in the same way: They create an Element on a specific event, append it (mostly) to the document, and destroy it on another (or the same) one. You just need to respect one thing: Don’t create a Tooltip, when it is still there. Our small script below use the same system, it creates the Tooltip everytime the cursor hovers and element and destroys it when the element gets leaved. To keep our rule alive, we assign an unique ID to each element and Tooltip. So it will “skip” the creation process and use the same Tooltip, if the user hovers the element again bevor it gets destroyed.

Sounds easy? It is, check out our code:

var toolID = 0, tooltip = function(ev){
    if(ev.type === "mouseenter"){
        var config = {
            color: "white", 
            position: "top", 
            animation: "blank", 
            classNames: ""
        }, tip, pos;

        if(!this.hasAttribute("data-tooltip-id")){
            if(this.hasAttribute("data-tooltip-config")){
                this.getAttribute("data-tooltip-config").split(",").forEach(function(item){
                    if(["white", "black", "red", "orange", "green", "blue", "violet"].indexOf(item) >= 0){
                        config.color = item;
                    } else if(["top", "left", "right", "bottom"].indexOf(item) >= 0){
                        config.position = item;
                    } else if(["blank", "fade", "ease-in", "ease-out"].indexOf(item) >= 0){
                        config.animation = item;
                    } else {
                        config.classNames += " " + item;
                    }
                });
            }
            tip = document.createElement("DIV");
            tip.id = "tooltip-" + ++toolID;
            tip.innerHTML = this.getAttribute("data-tooltip");
            tip.className = "tooltip tooltip-" + config.color + " tooltip-" + config.position + " "
                          + "tooltip-" + config.animation + config.classNames;
            document.body.appendChild(tip);

            pos = function(element){
                var position = {
                    top:    element.offsetTop    || 0,
                    left:   element.offsetLeft   || 0,
                    width:  element.offsetWidth  || 0,
                    height: element.offsetHeight || 0
                };
                while(element = element.offsetParent){
                    position.top  += element.offsetTop;
                    position.left += element.offsetLeft;
                }
                return position;
            }(this);
            switch(config.position){
                case "left":
                    tip.style.top = (pos.top + (pos.height/2) - (tip.offsetHeight/2)) + "px";
                    tip.style.left = (pos.left - tip.offsetWidth - 10) + "px";
                    break;
                case "right":
                    tip.style.top = (pos.top + (pos.height/2) - (tip.offsetHeight/2)) + "px";
                    tip.style.left = (pos.left + pos.width + 10) + "px";
                    break;
                case "bottom":
                    tip.style.top = (pos.top + pos.height + 10) + "px";
                    tip.style.left = (pos.left + (pos.width / 2) - (tip.offsetWidth / 2)) + "px";
                    break;
                default:
                    tip.style.top = (pos.top - tip.offsetHeight - 10) + "px";
                    tip.style.left = (pos.left + (pos.width / 2) - (tip.offsetWidth / 2)) + "px";
                    break;
            }
            this.setAttribute("data-tooltip-id", "tooltip-" + toolID);
        }
        (function(id){
            setTimeout(function(){ document.querySelector("#" + id).className += " show" }, 25);
        }(this.getAttribute("data-tooltip-id")));
        return;
    }
    
    if(this.hasAttribute("data-tooltip-id")){
        var tip = document.querySelector("#" + this.getAttribute("data-tooltip-id"));
        tip.className = tip.className.replace(/(?:^|\\s+)(show)(?:\\s+|$)/, "");
        this.removeAttribute("data-tooltip-id");
        (function(e){
            setTimeout(function(){ e.parentElement.removeChild(e); }, 200);
        })(tip);
    }
    return;
};

Of course, the script isn’t perfect at all and completely depends on a fitted Stylesheet, which takes over the colour and the animation process. The “destroy” part within the code also just “assumes” that the “hide” animation is completed by waiting 200 milliseconds… This MAY leads to some errors if you hover the element abnormal often and fast. The only issue I mentioned is that the script “completes” the shown process even if the user doesn’t hover the element anymore. So nothing really disturbing, but…

 

… how does it work in general?

The script itself is just a single function, which does nothing without your help. So an respective loop, which attaches our function to all available / possible elements need to be written too. We’re using the same script on this website and initialize it as follows:

document.addEventListener("DOMContentLoaded", function(){
    var convert = document.querySelectorAll("abbr[title],acronym[title],dfn[title]");
    [].forEach.call(convert, function(item){
        this.setAttribute("data-tooltip", this.getAttribute("title"));
        this.setAttribute("data-tooltip-config", "black,ease-in");
    });

    var tooltips = document.querySelectorAll("*[data-tooltip]");
    [].forEach.call(tooltips, function(item){
        item.addEventListener("mouseenter", tooltip);
        item.addEventListener("mouseleave", tooltip);
    });
});

The few lines above loops every element, having a data-tooltip attribute, and attaches the mouseenter as well as the mouseleave events to our tooltip function… But that’s not all, of course. Before it handles every possible tooltip element, it converts each <abbr>, <acronym> and <dfn> element (which has at least a “title” attribute) into a valid tooltip (so it just adds a data-tooltip attribute using the same value as on “title”).

 

As you may recognize from the code above, every element MUST contain the data-tooltip attribute, which contains the tooltip text (including HTML). You can configure the colour, the animation behaviour as well as the position using the additional data-tooltip-config attribute. This value can contain one or more of the following settings (use just a comma-separated string):

white, black, red, orange, green, blue or violet
The supported colours, you can use just ONE, of course. The desired colour gets just added as “tooltip-{color}” class name to the main tooltip object. The coloring happens through the CSS / Less Stylesheet ONLY (as shown below)!
top, left, right or bottom
The supported positions, you can use just ONE, of course. The desired position will added as “tooltip-position” class name to the main tooltip object. But also gets used to calculate the absolute position of the tooltip element. The CSS / Less Styles are important as well for the animations and the small pointing triangle.
blank, fade, ease-in or ease-out
The supported animations, you can use just ONE, of course. The desired animation gets just added as “tooltip-{animation}” class name to the main tooltip object. The animation happens through the CSS / Less Stylesheet ONLY (as shown below)!
*
You can pass also as many additional class names as possible. Each comma-separated value which doesn’t meets one of the above strings will plainly added to the main tooltip object, which allows additional CSS / Less based settings.

 

HowTo Design Tooltips (CSS/Less Part)

You may think 80 Lines of JavaScript Code for the core, 7 colours and 3 different animations, that’s really neat! But no… It’s not that easy! Our Tooltip solution handles the main part on the CSS side. So the “fade”, “easy-in” and “easy-out” animations are just CSS 3 transitions, which we already learned in this Just Vanilla article! the following code uses Less as CSS Pre-Processor, but you can click on the demonstration page below, which also offers a rendered CSS Source Code!

// Color Variables
@white:     #FFFFFF;
@black:     #202428;
@red:       #DC4B46;
@orange:    #DC9650;
@green:     #19C878;
@blue:      #4B64C8;
@violet:    #A53278;

// Tooltip Design
*[data-tooltip]{
   cursor: pointer;
}
.tooltip{
    width: auto;
    margin: 0;
    padding: 10px 15px;
    opacity: 1.0;
    z-index: 999;
    display: inline-block;
    opacity: 0;
    position: absolute;
    font-size: 12px;
    line-height: 1.25;
    border-radius: 3px;
    -moz-border-radius: 3px;
    -webkit-border-radius: 3px;
    
    &.show{
        opacity: 1.0;
    }
    &:after{
        width: 0;
        height: 0;
        content: "";
        display: block;
        position: absolute;
    }

    // Tooltip Positions
    &.tooltip-top{
        &:after{
            top: 100%;
            left: 50%;
            margin: 0 0 0 -10px;
            border-width: 10px 10px 0 10px;
            border-style: solid;
            border-color: @white transparent transparent transparent;
        }
    }
    &.tooltip-left{
        &:after{
            top: 50%;
            left: 100%;
            margin: -10px 0 0 0;
            border-width: 10px 0 10px 10px;
            border-style: solid;
            border-color: transparent transparent transparent @white;
        }
    }
    &.tooltip-right{
        &:after{
            top: 50%;
            left: -10px;
            margin: -10px 0 0 0;
            border-width: 10px 10px 10px 0;
            border-style: solid;
            border-color: transparent @white transparent transparent;
        }
    }
    &.tooltip-bottom{
        &:after{
            top: -10px;
            left: 50%;
            margin: 0 0 0 -10px;
            border-width: 0 10px 10px 10px;
            border-style: solid;
            border-color: transparent transparent @white transparent;
        }
    }

    // Tooltip Colors
    &.tooltip-red{
        color: @white;
        background-color: @red;

        &.tooltip-top:after{
            border-top-color: @red;
        }
        &.tooltip-left:after{
            border-left-color: @red;
        }
        &.tooltip-right:after{
            border-right-color: @red;
        }
        &.tooltip-bottom:after{
            border-bottom-color: @red;
        }
    }
    &.tooltip-orange{
        color: @white;
        background-color: @orange;

        &.tooltip-top:after{
            border-top-color: @orange;
        }
        &.tooltip-left:after{
            border-left-color: @orange;
        }
        &.tooltip-right:after{
            border-right-color: @orange;
        }
        &.tooltip-bottom:after{
            border-bottom-color: @orange;
        }
    }
    &.tooltip-green{
        color: @white;
        background-color: @green;

        &.tooltip-top:after{
            border-top-color: @green;
        }
        &.tooltip-left:after{
            border-left-color: @green;
        }
        &.tooltip-right:after{
            border-right-color: @green;
        }
        &.tooltip-bottom:after{
            border-bottom-color: @green;
        }
    }
    &.tooltip-blue{
        color: @white;
        background-color: @blue;

        &.tooltip-top:after{
            border-top-color: @blue;
        }
        &.tooltip-left:after{
            border-left-color: @blue;
        }
        &.tooltip-right:after{
            border-right-color: @blue;
        }
        &.tooltip-bottom:after{
            border-bottom-color: @blue;
        }
    }
    &.tooltip-violet{
        color: @white;
        background-color: @violet;

        &.tooltip-top:after{
            border-top-color: @violet;
        }
        &.tooltip-left:after{
            border-left-color: @violet;
        }
        &.tooltip-right:after{
            border-right-color: @violet;
        }
        &.tooltip-bottom:after{
            border-bottom-color: @violet;
        }
    }
    &.tooltip-black{
        color: @white;
        background-color: darken(@black, 7%);

        &.tooltip-top:after{
            border-top-color: darken(@black, 7%);
        }
        &.tooltip-left:after{
            border-left-color: darken(@black, 7%);
        }
        &.tooltip-right:after{
            border-right-color: darken(@black, 7%);
        }
        &.tooltip-bottom:after{
            border-bottom-color: darken(@black, 7%);
        }
    }
    &.tooltip-white{
        color: @black;
        background-color: darken(@white, 3%);

        &.tooltip-top:after{
            border-top-color: darken(@white, 3%);
        }
        &.tooltip-left:after{
            border-left-color: darken(@white, 3%);
        }
        &.tooltip-right:after{
            border-right-color: darken(@white, 3%);
        }
        &.tooltip-bottom:after{
            border-bottom-color: darken(@white, 3%);
        }
    }

    // Animation :: Blank
    &.tooltip-blank{
        opacity: 0;
        
        &.show{
            opacity: 1.0;
        }
    }

    // Animation :: Fade
    &.tooltip-fade{
        opacity: 0;
        transition: opacity 142ms linear;
        -moz-transition: opacity 142ms linear;
        -webkit-transition: opacity 142ms linear;
        
        &.show{
            opacity: 1.0;
        }
    }

    // Animation :: Ease-In
    &.tooltip-ease-in{
        transition: margin 142ms linear, opacity 142ms linear;
        -moz-transition: margin 142ms linear, opacity 142ms linear;
        -webkit-transition: margin 142ms linear, opacity 142ms linear;

        &.tooltip-top{
            margin: -25px 0 0 0;
        }
        &.tooltip-left{
            margin: 0 0 0 -25px;
        }
        &.tooltip-right{
            margin: 0 0 0 25px;
        }
        &.tooltip-bottom{
            margin: 25px 0 0 0;
        }
        &.tooltip-top.show,
        &.tooltip-left.show,
        &.tooltip-right.show,
        &.tooltip-bottom.show{
            margin: 0;
        }
    }

    // Animation :: Ease-Out
    &.tooltip-ease-out{
        opacity: 0;
        transition: margin 142ms linear, opacity 142ms linear;
        -moz-transition: margin 142ms linear, opacity 142ms linear;
        -webkit-transition: margin 142ms linear, opacity 142ms linear;

        &.show{
            margin: 0;
            opacity: 1.0;
        }
        &.tooltip-top{
            margin: 15px 0 0 0;
        }
        &.tooltip-left{
            margin: 0 0 0 15px;
        }
        &.tooltip-right{
            margin: 0 0 0 -15px;
        }
        &.tooltip-bottom{
            margin: -15px 0 0 0;
        }
        &.tooltip-top.show,
        &.tooltip-left.show,
        &.tooltip-right.show,
        &.tooltip-bottom.show{
            margin: 0;
        }
    }
}

 

HowTo Demonstrate

We’re using this script on our website (look at the Showcase on the front page, or just search for abbr, acronym or dfn elements on our articles). You can also click on the following button, which will redirect you to a small demonstration page on our codepen.io Pen.

 

pytesNET Tooltip Paradise @ codepen.io