Luigi Cavalieri - Authoring Open Source Code

Animated Tooltip Landing from All Directions with CSS

A how-to on making a CSS-only tooltip to fade in from any direction by just changing a CSS class.

Animated Tooltip Landing from All Directions with CSS

In this second post about tooltips I want to describe to you some new code developed on top of the CSS cascade I published about a month ago, and which I used to draw a CSS-only tooltip. I'm going to start with a slight refactoring and then add up on it new CSS rules, which will allow the tooltip to appear with a fade-in animation from one of the four main directions by just changing a CSS class.

The Initial Codebase

The CSS cascade I am talking about was the subject of my previous post (Drawing a Tooltip with CSS: Can a Border Give Shape to a Triangle?) where I described how the following minimalistic markup could look like a tooltip appearing on top of a disk with a question mark in the middle:

1<span class="tooltip-wrapper">
2  <span class="tooltip">I'm a Tooltip!</span>
3  <span class="help-point">?</span>
4</span>

The whole cascade styling the above markup is the one below:

1.tooltip-wrapper {
2  display: inline-flex;
3  justify-content: center;
4  position: relative;
5  }
6  .tooltip-wrapper:hover .tooltip {
7    display: flex;
8  }
9  .tooltip-wrapper .tooltip {
10    background-color: #333;
11    border-radius: 4px;
12    bottom: calc( 100% + 0.6em + 2px );
13    box-shadow: 0px 2px 4px #07172258;
14    color: #fff;
15    display: none;
16    font-size: 0.68rem;
17    justify-content: center;
18    line-height: 1.35em;
19    padding: 0.5em 0.7em;
20    position: absolute;
21    text-align: center;
22    width: 7rem;
23    z-index: 1;
24  }
25  .tooltip-wrapper .tooltip::before {
26    border-width: 0.6em 0.8em 0;
27    border-color: transparent;
28    border-top-color: #333;
29    content: "";
30    display: block;
31    border-style: solid;
32    position: absolute;
33    top: 100%;
34  }
35  .tooltip-wrapper .help-point {
36    align-items: center;
37    background-color: #333;
38    border-radius: 50%;
39    color: #fff;
40    cursor: default;
41    display: flex;
42    font-size: 0.65rem;
43    font-weight: bold;
44    height: 1rem;
45    justify-content: center;
46    width: 1rem;
47  }

And it produces this result:

CSS-only tooltip.
CSS-only tooltip.

The aim of this post is to enrich the CSS code just presented so as to be able to make the tooltip to slowly appear into view from a given direction by just adding .top, .bottom, .right or .left to the element .tooltip-wrapper.

The First Step is Refactoring

Not a big one, just a small refactoring to make the fade-in animation work without the need to resort to JavaScript.

The problem is this: as you can read from the code above, right now, the tooltip visibility is controlled by just a plain display rule. Generally speaking, when we turn off the display of an element, the webpage's markup is rendered as though the element did not exist. So it turns out that the display: none rule used to hide the tooltip would definitely be the breaking point of an animation based on transitioning the value of the opacity property.

So, the first thing to do is to remove the display: none rule from .tooltip and control its visibility by just playing with the value of opacity. However, this change alone isn't enough to make the tooltip completely disappear from the screen, because when opacity is equal to zero .tooltip can still receive mouse events, thus hindering the user from clicking on something which might be behind the tooltip itself. To get around this slight hindrance, the visibility rule comes to our rescue, and the small refactoring we need boils down to the following:

1.tooltip-wrapper {
2  /* ... */
3  }
4  .tooltip-wrapper:hover .tooltip {
5    /* display: flex; */
6
7    opacity: 1;
8    visibility: visible;
9  }
10  .tooltip-wrapper .tooltip {
11    /* ... */
12    
13    /* display: none; */
14    
15    display: flex;
16    opacity: 0;
17    transition: all 0.3s ease-in;
18    visibility: hidden;
19  }
20  .tooltip-wrapper .tooltip::before {
21    /* ... */
22  }
23  .tooltip-wrapper .help-point {
24    /* ... */
25  }

The tooltip is still working as before, the only thing changed is that the tooltip no longer appears abruptly into view, now it discloses itself to the user with a fade-in animation.

As anticipated, the main code just refactored isn't going to undergone other changes, what we want to do instead, is to write some new code in such a way that it can be applied to our markup through the addition of a CSS class — we want to make our tooltip configurable.

Landing from the Top

Tooltip landing from the top

With a little trick, we are going to improve how the tooltip appears into view. Right now it just fades in. What if we make it to "land from the top" while it fades in? I think the animation would gain in beauty, don't you guess so?

All right, no more words, just a margin-bottom: if we distance .tooltip a little bit more from .help-point and then we gradually reduce that gap to zero as the mouse's pointer hovers the .tooltip-wrapper, we hit the target. The code needed is minimal:

1.tooltip-wrapper.top .tooltip {
2  margin-bottom: 8px;
3}
4.tooltip-wrapper.top:hover .tooltip {
5  margin-bottom: 0;
6}

To appreciate the animation we just have to add the class top to the .tooltip-wrapper element.

The margin-bottom property is automatically animated thanks to the fact that we applied the transition rule to all the properties of .tooltip which change in value during the hover event:

1.tooltip-wrapper .tooltip {
2  /* ... */
3
4  transition: all 0.3s ease-in;
5}

Landing from the Bottom

Tooltip landing from the bottom

To make the tooltip appear from the bottom by adding the class .bottom to the .tooltip-wrapper element, we need very little code, because all we have to do is to reposition .tooltip (the rectangle composing the tooltip) and .tooltip::before (the small triangle characterising the tooltip). Obviously the triangle must also be rotated by 180 degrees to make it point upward:

1.tooltip-wrapper.bottom .tooltip {
2  /**
3   * 100% of the height of .tooltip-wrapper
4   *
5   * 0.6em is the height of the triangle ( .tooltip::before )
6   *
7   * 2px is an arbitrary blank gap between
8   * the tip of the triangle and .help-point
9   */
10  top: calc( 100% + 0.6em + 2px );
11  margin-top: 8px;
12}
13.tooltip-wrapper.bottom:hover .tooltip {
14  margin-top: 0;
15}
16.tooltip-wrapper.bottom .tooltip::before {
17  transform: rotate( 180deg );
18  
19  /* 100% of the height of .tooltip */
20  bottom: 100%;
21}

There is a but: if you take a look at the code we started from, the absolute positioning of .tooltip and .tooltip::before is controlled respectively through the bottom and the top properties. So, to let the above code work without unexpected behaviours, we need to reset these two properties to their default values. This keeps being true even when the tooltip appears from the right or the left, the reset must work in all cases except when the class top is added to the .tooltip-wrapper element:

1.tooltip-wrapper:not(.top) .tooltip {
2  bottom: auto;
3}
4.tooltip-wrapper:not(.top) .tooltip::before {
5  top: auto;
6}

Landing from the Right

Tooltip landing from the right

For the landing of the tooltip from the right we need a little bit more of code, because we don't have to only reposition .tooltip and .tooltip::before, but we also have to align those elements vertically relative to either .tooltip-wrapper and .help-point:

1.tooltip-wrapper.right,
2.tooltip-wrapper.left {
3  /* Vertically centres .tooltip */
4  align-items: center;
5}
6.tooltip-wrapper.right .tooltip,
7.tooltip-wrapper.left .tooltip {
8  /* Vertically centres the triangle (.tooltip::before) */
9  align-items: center;
10
11  /* We limit the height to make the tooltip look nicer. */
12  min-height: 2.5em;
13}
14.tooltip-wrapper.right .tooltip {
15  left: calc( 100% + 0.6em + 2px );
16  margin-left: 8px;
17}
18.tooltip-wrapper.right:hover .tooltip {
19  margin-left: 0;
20}
21.tooltip-wrapper.right .tooltip::before {
22  right: calc( 100% - 0.5em - 0.25px );
23  transform: rotate( 90deg );
24}

You might be wondering from where that 0.5em in the calc() operation comes from. And you might be justified to be perplexed.

When we rotate the triangle by 90 degrees, because the height and the base of the triangle are not equal in value, the triangle undergoes a translation too. The latter is a side effect of the automatic vertical alignment achieved through the flexbox rule align-items: center. It follows that if we applied only right: 100% (where 100% is relative to the width of .tooltip) there would be a small blank gap between the triangle and the left side of .tooltip:

Re-alignment side effect
Blank gap appearing in the event was applied a plain right: 100%.

However, you don't need to roll the dice to found out what the length of that small blank gap is. You just have to use the following empiric formula:

1H = height of the triangle
2B = base of the triangle
3
4GAP = ( B - H ) / 2;

In fact, in our example, since B = 1.6em and H = 0.6em, the length of the blank gap is exactly equal to 0.5em.

The 0.25px included in the same calc() operation is just a correcting factor: if we remove it, the blank gap would still be visible while the animation is running, although only as a very thin flickering line which just a trained eye would glimpse.

Remember, bugs are there to keep our creativity fit!

Landing from the Left

Tooltip landing from the left

As you might have noticed from the section above, some of the rules written for the "landing from the right" are the same, and even the reasoning is the same, the only thing changing is the direction. So the code is basically the one just seen, but mirrored:

1.tooltip-wrapper.left .tooltip {
2  right: calc( 100% + 0.6em + 2px );
3  margin-right: 8px;
4}
5.tooltip-wrapper.left:hover .tooltip {
6  margin-right: 0;
7}
8.tooltip-wrapper.left .tooltip::before {
9  left: calc( 100% - 0.5em - 0.25px );
10  transform: rotate( -90deg );
11}

We finally have all the code needed to make the tooltip appear into view from the direction specified by the CSS class we add to .tooltip-wrapper. For instance, the following will produce a tooltip "landing from the left":

1<span class="tooltip-wrapper left">
2  <span class="tooltip">Thanks for reading!</span>
3  <span class="help-point">?</span>
4</span>