0
\$\begingroup\$

I always disliked how basic HTML tooltips are and often find myself employing modals to display more information, but it feels like the wrong solution from a UX perspective in most cases. To that end, I have put together the following solution.

Due to the self-taught nature of my work, I fully expect there is a lot of room for improvement and would be most grateful to anyone who can identify and explain anything I have done inefficiently, or downright badly.

I have employed jQuery within this, which was entirely unnecessary and was done so for brevity. I am interested in opinions (and research!) which would indicate if there are any disadvantages to this. Most sites I work on would be using Twitter Bootstrap and so would have jQuery included anyway. Would a purely vanilla solution offer any noticeable advantages? Could it be done without making the code more messy and more difficult to follow?

$(function() {
    // SETTINGS
    var distanceFromCursor = 5; // pixel offset from cursor to top-left of tooltip
    var zIndexBase = 100; // z-index start point
    
    
    
    // INIT
    tooltipUnstickyAll(); // set default behaviour to follow the cursor
    tooltipRaiseAll(zIndexBase); // set z-index on all tooltips

    
    
    // FUNCTIONS
    function getTarget(e) {
        return $("#tooltip" + e.target.attributes['data-tooltip'].value).eq(0); // return a jQuery object representation of the tooltip     element relating to the data-tooltip value
    }
    function isSticky(e) {
        return getTarget(e).attr('data-sticky'); // return value of data-sticky for the relevant tooltip, should always be true|false
    }
    function tooltipUnstickyAll() {
        $(".tooltip").attr('data-sticky', false); // make sure all tooltips are unsticky to begin with, so they will always appear first time
    }
    function tooltipShow(target, x, y, z) {
        $(".tooltip[data-sticky='false']").hide(); // make sure all non-sticky tooltips are hidden
        target.show(); // display the current tooltip
        tooltipMove(target, x, y); // move tooltip to the cursor
        tooltipRaise(target, z);
    }
    function tooltipHide(target) {
        target.hide(); // hide the current tooltip
    }
    function tooltipMove(target, x, y) {
        target[0].style.top = (y + distanceFromCursor) + 'px'; // move the tooltip to match the cursor
        target[0].style.left = (x + distanceFromCursor) + 'px'; // move the tooltip to match the cursor
    }
    function tooltipStickyToggle(target) {
        target.attr('data-sticky',target.attr('data-sticky')=="false"); // toggle value of data-sticky attribute of the target between     true|false
    }
    function tooltipHideAll() {
        $(".tooltip").hide(); // hide all the tooltips
    }
    function tooltipRaiseAll(z) {
        $(".tooltip").css('z-index', z); // set z-index on all tooltips
    }
    function tooltipRaise(target, z) {
        target.css('z-index', z); // set z-index on target
    }
    function highestZindex() {
        indexHighest = zIndexBase;
        $(".tooltip").each(function() {
            var indexCurrent = parseInt($(this).css("z-index"), 10);
            if(indexCurrent > indexHighest) {
                indexHighest = indexCurrent;
            }
        });
        return indexHighest + 1;
    }

    
    
    // EVENTS
    $(".tooltipable")
        .mouseover(function(e) {
            if (isSticky(e)=="false") tooltipShow(getTarget(e), e.clientX, e.clientY, highestZindex());
        })
        .mouseout(function(e) {
            if (isSticky(e)=="false") tooltipHide(getTarget(e));
        })
        .mousemove(function(e) {
            if (isSticky(e)=="false") tooltipMove(getTarget(e), e.clientX, e.clientY);
        })
        .click(function(e) {
            tooltipStickyToggle(getTarget(e));
            if (isSticky(e)=="false") tooltipMove(getTarget(e), e.clientX, e.clientY);
        })
        .dblclick(function(e) {
            tooltipHideAll();
            tooltipUnstickyAll();
            e.preventDefault();
        });
});
.tooltip {
    display:none;
    position:absolute;
    padding: 8px;
    border-radius: 15px;
    border: 2px solid #73AD21;
    background: #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<h1>Hover your cursor over some of the images</h1>
<h4>Click to toggle tooltip stickyness</h4>
<h4>Double click to hide all tooltips</h4>

<!--
Anything you want to have a tooltip
Just give it class="tooltipable" and set data-tooltip= any value you want
Numbers are used here but you can use what you like, names might make more sense in some contexts
-->
<img class="tooltipable" data-tooltip="1" src="https://placeholdit.imgix.net/~text?txtsize=19&amp;txt=200%C3%97300&amp;w=200&amp;h=300">
<img class="tooltipable" data-tooltip="2" src="https://placeholdit.imgix.net/~text?txtsize=19&amp;txt=150%C3%97300&amp;w=150&amp;h=300">
<img class="tooltipable" data-tooltip="3" src="https://placeholdit.imgix.net/~text?txtsize=19&amp;txt=320%C3%97300&amp;w=320&amp;h=300">

<!--
Create a div for each tooltip
The class="tooltip" is entirely a styling thing, it's not necessary for functionality
Just give it id="tooltipX" where X corresponds to the data-tooltip value used elsewhere
Fire any markup you want in the divs and away you go
-->
<div class="tooltip" id="tooltip1">
    Put some text in here
    <p>Make it multiline</p>
    Some of it can be <strong>bold</strong> or whatever you want
</div>
<div class="tooltip" id="tooltip2">
    Be inventive, fire some <a href="https://google.co.uk">links</a> into it
</div>
<div class="tooltip" id="tooltip3">
    Or go crazy with some images like this one
    <br>
    <img src="https://placeholdit.imgix.net/~text?txtsize=19&amp;txt=80%C3%9760&amp;w=80&h=60">
</div>

\$\endgroup\$
  • 2
    \$\begingroup\$ took the liberty of converting your code blocks to live stack snippet \$\endgroup\$ – Flambino Mar 27 '17 at 16:06
4
\$\begingroup\$

You are using $(".tooltipable") to select elements to bind events. And you use .tooltipable to indicate which element should have tooltip. This may make users think that any element with this class can have tooltip. But newly added elements can't. Generally, you can solve the problem like this:

$("body")
    .on("mouseover",".tooltipable",function(e) {

or just write it as a jQuery plugin:

$.fn.tooltip=function(tipId){
     this.mouseover(function(){
     ...
     });
}

Live demo(may not be needed here though):

$(function() {



  // SETTINGS
  var distanceFromCursor = 5; // pixel offset from cursor to top-left of tooltip
  var zIndexBase = 100; // z-index start point



  // INIT
  tooltipUnstickyAll(); // set default behaviour to follow the cursor
  tooltipRaiseAll(zIndexBase); // set z-index on all tooltips



  // FUNCTIONS
  function getTarget(e) {
    return $("#tooltip" + e.target.attributes['data-tooltip'].value).eq(0); // return a jQuery object representation of the tooltip     element relating to the data-tooltip value
  }

  function isSticky(e) {
    return getTarget(e).attr('data-sticky'); // return value of data-sticky for the relevant tooltip, should always be true|false
  }

  function tooltipUnstickyAll() {
    $(".tooltip").attr('data-sticky', false); // make sure all tooltips are unsticky to begin with, so they will always appear first time
  }

  function tooltipShow(target, x, y, z) {
    $(".tooltip[data-sticky='false']").hide(); // make sure all non-sticky tooltips are hidden
    target.show(); // display the current tooltip
    tooltipMove(target, x, y); // move tooltip to the cursor
    tooltipRaise(target, z);
  }

  function tooltipHide(target) {
    target.hide(); // hide the current tooltip
  }

  function tooltipMove(target, x, y) {
    target[0].style.top = (y + distanceFromCursor) + 'px'; // move the tooltip to match the cursor
    target[0].style.left = (x + distanceFromCursor) + 'px'; // move the tooltip to match the cursor
  }

  function tooltipStickyToggle(target) {
    target.attr('data-sticky', target.attr('data-sticky') == "false"); // toggle value of data-sticky attribute of the target between     true|false
  }

  function tooltipHideAll() {
    $(".tooltip").hide(); // hide all the tooltips
  }

  function tooltipRaiseAll(z) {
    $(".tooltip").css('z-index', z); // set z-index on all tooltips
  }

  function tooltipRaise(target, z) {
    target.css('z-index', z); // set z-index on target
  }

  function highestZindex() {
    indexHighest = zIndexBase;
    $(".tooltip").each(function() {
      var indexCurrent = parseInt($(this).css("z-index"), 10);
      if (indexCurrent > indexHighest) {
        indexHighest = indexCurrent;
      }
    });
    return indexHighest + 1;
  }

  // EVENTS
  $("body")
    .on("mouseover",".tooltipable",function(e) {
      if (isSticky(e) == "false") tooltipShow(getTarget(e), e.clientX, e.clientY, highestZindex());
    })
    .on("mouseover",".tooltipable",function(e) {
      if (isSticky(e) == "false") tooltipHide(getTarget(e));
    })
    .on("mousemove",".tooltipable",function(e) {
      if (isSticky(e) == "false") tooltipMove(getTarget(e), e.clientX, e.clientY);
    })
    .on("click",".tooltipable",function(e) {
      tooltipStickyToggle(getTarget(e));
      if (isSticky(e) == "false") tooltipMove(getTarget(e), e.clientX, e.clientY);
    })
    .on("dbclick",".tooltipable",function(e) {
      tooltipHideAll();
      tooltipUnstickyAll();
      e.preventDefault();
    });
});
.tooltip {
  display: none;
  position: absolute;
  padding: 8px;
  border-radius: 15px;
  border: 2px solid #73AD21;
  background: #ddd;
}
.tooltipable{
}
<!DOCTYPE html>
<html lang="en">

<head>
  <title>Tooltip Test</title>
  <link rel="stylesheet" type="text/css" href="tooltip.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="tooltip.js"></script>
</head>

<body>
  <h1>Hover your cursor over some of the images</h1>
  <h4>Click to toggle tooltip stickyness</h4>
  <h4>Double click to hide all tooltips</h4>

  <!--
Anything you want to have a tooltip
Just give it class="tooltipable" and set data-tooltip= any value you want
Numbers are used here but you can use what you like, names might make more sense in some contexts
-->
  <img class="tooltipable" data-tooltip="1" src="https://placeholdit.imgix.net/~text?txtsize=19&amp;txt=200%C3%97300&amp;w=200&amp;h=300">
  <img class="tooltipable" data-tooltip="2" src="https://placeholdit.imgix.net/~text?txtsize=19&amp;txt=150%C3%97300&amp;w=150&amp;h=300">
  <img class="tooltipable" data-tooltip="3" src="https://placeholdit.imgix.net/~text?txtsize=19&amp;txt=320%C3%97300&amp;w=320&amp;h=300">

  <!--
Create a div for each tooltip
The class="tooltip" is entirely a styling thing, it's not necessary for functionality
Just give it id="tooltipX" where X corresponds to the data-tooltip value used elsewhere
Fire any markup you want in the divs and away you go
-->
  <div class="tooltip" id="tooltip1">
    Put some text in here
    <p>Make it multiline</p>
    Some of it can be <strong>bold</strong> or whatever you want
  </div>
  <div class="tooltip" id="tooltip2">
    Be inventive, fire some <a href="https://google.co.uk">links</a> into it
  </div>
  <div class="tooltip" id="tooltip3">
    Or go crazy with some images like this one
    <br>
    <img src="https://placeholdit.imgix.net/~text?txtsize=19&amp;txt=80%C3%9760&amp;w=80&h=60">
  </div>

</body>

</html>

\$\endgroup\$
  • \$\begingroup\$ That's a great point, I hadn't realised that limitation of dynamically created elements. After reading stackoverflow.com/questions/203198/… I now have a better understanding \$\endgroup\$ – Darren H Mar 27 '17 at 18:29
  • \$\begingroup\$ Can I assume .on("mouseover",".mouseout",function(e) { is a typo in your version of the snippet? It doesn't make any sense to me as there is no mouseout class \$\endgroup\$ – Darren H Mar 27 '17 at 18:55
  • \$\begingroup\$ FYI there are a number of jQuery tooltips plugins already out there which you may want to consider. \$\endgroup\$ – Mike Brant Mar 27 '17 at 21:15
  • \$\begingroup\$ @MikeBrant are there any in particular you have used and found to be suitable? I like the idea of building my own functionality where possible as I treat it as a learning exercise. But I am also keen to investigate others that are in circulation as I could certainly learn from their example \$\endgroup\$ – Darren H Mar 27 '17 at 22:44
  • \$\begingroup\$ @DarrenH Yes it's a typo, I fixed it. \$\endgroup\$ – blackmiaool Mar 28 '17 at 1:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.