<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js@*" data-semver="1.2.5" src="http://code.angularjs.org/1.2.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app">
<h1>Tooltips, various usage scenarios</h1>
<p>Style guide screenshot:</p>
<img alt="tooltip design" border=1 src="//dev/test/matthias-dailey/images/lu-styleguide/tooltip.png">
<div class="container">
<button tooltip="#tt0" style="top:50%; left:50%">Hover over me</button>
<button class="top right" tooltip="#tt1">Hover me</button>
<!-- covered by the reload button -->
<button tooltip="#tt2" style="top:50%; left:-60px;">#2 special</button>
<button tooltip="#tt2" style="top:50%; right:-60px;">#6 special</button>
<button class="bottom left" tooltip="#tt3">#3 BL</button>
<button class="bottom right" tooltip="#tt4">#4 BR</button>
<button class="bottom" tooltip="#tt5" style="right:70px">#5 Wide</button>
<button class="bottom" tooltip="#tt6" style="right:170px">#6 White</button>
<div id="tt0" class="tooltip">This is a tooltip.<br>It is pretty <b>awesome</b>.<br><img src="//dev/test/dancing-banana.gif"></div>
<div id="tt1" class="tooltip">Tooltip forced below the element</div>
<div id="tt2" class="tooltip">Tooltip does not go outside the screen</div>
<div id="tt3" class="tooltip">s<!-- small --></div>
<div id="tt4" class="tooltip">Multiline like a boss<br>Line 2<br>Line 3<hr>oops, that was a horizontal rule</div>
<div id="tt5" class="tooltip">
<b>This text would have overflowed.</b>
Wayfarers slow-carb Helvetica cornhole single-origin coffee Williamsburg. Pop-up PBR&B lo-fi Tumblr asymmetrical, readymade skateboard next level. Aesthetic wayfarers plaid vegan Shoreditch. PBR&B locavore kogi Wes Anderson Terry Richardson. Disrupt letterpress keytar, Williamsburg salvia Pitchfork bespoke iPhone pour-over lo-fi fanny pack vegan whatever Intelligentsia chillwave. Polaroid Williamsburg quinoa vinyl Odd Future American Apparel. 8-bit synth fixie, locavore sustainable ennui readymade raw denim food truck.
http://hipsteripsum.me
</div>
<div id="tt6" class="tooltip white">White with <br>no border</div>
</div>
</body>
</html>
/*
Tooltip directive
@author Matthias Dailey | http://zeroriginal.com
@version 1.0.3
Shows a tooltip on mouseover and click. Hides in on mouseout or blur.
The tooltip is an HTML element with `display:none`.
Positioned at the top by default, and offset to the left a little. If there is
no space on top, shown on bottom.
Tooltip colors, styles, and padding are controlled by your stylesheet.
The tooltip arrow is added and positioned automatically, and it matches the
background color and border color.
Requires browser support of `box-sizing:border-box` and `display:inline-block`.
Tooltip arrow is not present in IE versions 8 and below.
# Missing features:
- support for when a tooltip extends past the document body (cannot scroll to it)
Could use improvements:
1. More tooltip open/close detection (touch events)
2. Box-shadow support
Changelog:
1.0.2 - Caret has correct border-width even with `border:none`
1.0.3 - Caret border element is completely hidden when border is 0.
*/
var luStyle = angular.module('luStyle',[]);
luStyle.directive('tooltip', [function(){
return {
restrict: 'A',
link: function (scope, element, attrs) {
var id = attrs['tooltip'].match(/#?(.+)/)[1];
if (!id) throw "bad id";
var $tooltip = angular.element(document.getElementById(id));
if (!$tooltip[0]) throw "tooltip '"+id+"' not found";
// the root html element. For window dimensions
var html = document.getElementsByTagName('html')[0];
// get the tooltip border width
var getBorderWidth = function() {
// IE lte 8 case
if (!window.getComputedStyle) return 1; // EAT THIS AND LIKE IT, IE.
var s = getComputedStyle($tooltip[0]);
// if the border is none, the border-width is 0
if (s.getPropertyValue('border') == 'none') {
return 0;
}
else {
return parseInt(s.getPropertyValue('border-width'), 10);
}
};
// spacing values
// margin so that the tooltip is not touching any edges
var margin = 1;
// tooltip border width
var bdw = getBorderWidth();
// the size of the caret
var caretSize = 5;
// caret overlap on the target
var caretOvr = 0;
if (caretOvr>0) caretOvr = caretOvr + margin + bdw;
// the left offset from the caret midpoint, if the tooltip is wide enough
var leftOffset = -30;
$tooltip.show = function() {
this.css('display', 'inline-block');
};
$tooltip.hide = function() {
this.css('display', 'none');
};
$tooltip.hide();
// position the tooltip and make it visible
$tooltip.positionAndShow = function() {
// reset tooltip styles
$tooltip.css({
'position': 'fixed',
'z-index': '9999999',
'box-sizing': 'border-box',
'width': 'auto'
});
// show the tooltip now. Must be visible to get dimensions.
$tooltip.show();
// get tooltip element's metrics
var tt = getMetrics($tooltip[0]);
// get the target element's metrics
var tgt = angular.copy(element[0].getBoundingClientRect());
if (!tgt.width) {
tgt.width = tgt.right - tgt.left;
tgt.height = tgt.top - tgt.bottom;
}
tgt.mid_x = tgt.right - (tgt.width/2);
tgt.mid_y = tgt.bottom - (tgt.height/2);
// the window's screen dimensions
var screen = {
width: html.clientWidth,
height: html.clientHeight
};
// if the target element is wider than the leftOffset,
if (leftOffset < tgt.width/2) {
// make leftOffset bigger
leftOffset = -tgt.width/2
}
// if the tooltip width is too small for the leftOffset
if (tt.width/2 < -leftOffset) {
// reduce leftOffset
leftOffset = -tt.width/2;
}
// Position left
// if the tooltip is too wide for the screen
if (margin + tt.width + margin > screen.width) {
// make the tooltip full-width
tt.left = 0 + margin;
tt.width = screen.width - margin - margin;
}
// if the tooltip is less wide than the screen
else {
// if there isn't enough space to the right
if (tgt.mid_x + leftOffset + tt.width + margin > screen.width) {
tt.left = screen.width - margin - tt.width;
}
// if there isn't enough space to the left
else if (tgt.mid_x + leftOffset < 0 + margin) {
// place tooltip at left edge
tt.left = 0 + margin;
//tt.width = 'auto';
}
// if there are no overflow problems
else {
// place tooltip at middle of target, offset
tt.left = tgt.mid_x + leftOffset;
//tt.width = 'auto';
}
}
$tooltip.css({
'left': tt.left + 'px',
'width': tt.width + 'px'
})
// recompute the metrics for the tooltip, since we may have changed its width
tt = getMetrics($tooltip[0]);
// Position top
// if there's space above
if (tgt.top > margin + tt.height + caretSize + margin) {
// put it above
tt.top = tgt.top /* - margin*/ - caretSize - tt.height + caretOvr;
$tooltip.css('top', tt.top + 'px');
// caret arrow goes below
$tooltip.removeClass('arr-above');
$tooltip.addClass('arr-below');
}
else {
// put it below. If there's not enough space there either, then too bad
tt.top = tgt.top + tgt.height /* + margin*/ + caretSize - caretOvr;
$tooltip.css('top', tt.top + 'px');
// caret arrow goes below
$tooltip.addClass('arr-above');
$tooltip.removeClass('arr-below');
}
// position the caret at the middle of the target
setupCaret(tt, -tt.left + tgt.mid_x);
};
element.on('mouseover click', function(){
//scope.$apply(function(){ // not really necessary yet
$tooltip.positionAndShow();
//});
})
element.on('mouseout blur', function(){
//scope.$apply(function(){ // not really necessary yet
$tooltip.hide();
//});
});
var getMetrics = function(el) {
var area = getOffset(el);
return area;
};
// caret (can't be css. must be linked to JS for positioning)
var $caret, $caretborder;
$tooltip.append($caret).append($caretborder);
// Set the caret styles to match the computedStyle colors from the tooltip
// @param xpos the position of the caret midpoint, relative to the tooltip
var setupCaret = function (tt, xpos) {
if (!window.getComputedStyle) return; // NO CARET FOR YOU, IE lte 8!
// create or grab the carets
if ($tooltip.find('mark').length == 2) {
$caret = $tooltip.find('mark').eq(0);
$caretborder = $tooltip.find('mark').eq(1);
}
else {
$caret = angular.element('<mark class="caret">');
$caretborder = angular.element('<mark class="caret-border">');
}
$tooltip.append($caret).append($caretborder);
// min corner. If the caret is by the corner, enforce this min distance
var mc = 3;
// make the caret fit on the right side
if (xpos + caretSize + bdw > tt.width) xpos = tt.width - bdw - caretSize - mc - 1; // -1 for pixel sync
// make the caret fit on the left side
if (xpos < caretSize) xpos = caretSize + mc;
var generalStyles = {
'display': 'block',
'width': 0,
'height': 0,
'position': 'absolute',
'top': 'auto',
'left': xpos - caretSize + 'px',
'border': caretSize + 'px solid transparent',
'background': 'transparent',
'z-index': 2
};
$caret.css(generalStyles);
$caretborder.css(generalStyles);
if (bdw == 0) {
$caretborder.css('display','none');
}
var ttstyles = getComputedStyle($tooltip[0]);
var bgcolor = ttstyles.getPropertyValue('background-color');
var bordercolor = ttstyles.getPropertyValue('border-color');
// set top absolute position
if ($tooltip.hasClass('arr-above')) {
$caret.css({
'top': 0 - caretSize + 'px',
'bottom': 'auto',
'border-top': 'none',
'border-bottom': caretSize + 'px solid ' + bgcolor,
'z-index': 3
});
$caretborder.css({
'top': 0 - caretSize - bdw + 'px', // overlap the tooltip border
'bottom': 'auto',
'border-top': 'none',
'border-bottom': caretSize + 'px solid ' + bordercolor
});
}
else {
$caret.css({
'top': 'auto',
'bottom': 0 - caretSize + 'px',
'border-top': caretSize + 'px solid ' + bgcolor,
'border-bottom': 'none',
'z-index': 3
});
$caretborder.css({
'top': 'auto',
'bottom': 0 - caretSize - bdw + 'px', // overlap the tooltip border
'border-top': caretSize + 'px solid ' + bordercolor,
'border-bottom': 'none'
});
}
};
var getOffset = function(el) {
var x = el.offsetLeft,
y = el.offsetTop,
w = el.offsetWidth,
h = el.offsetHeight;
while (el = el.offsetParent) {
x += el.offsetLeft;
y += el.offsetTop;
}
return {
left: x,
top: y,
width: w,
height: h,
mid_x: x + (w/2),
mid_y: y + (h/2)
}
};
}
};
}])
angular.module('app', ['luStyle']);
/* Styles go here */
body {
font-size: 12px;
line-height:1;
}
.container {
/*border: 1px solid blue;*/
position: absolute;
top:0; left:0; right:0; bottom:0;
}
.container button {
position: absolute;
}
.top {top:0;}
.bottom {bottom:0;}
.left {left:0;}
.right {right:0;}
.tooltip {
position: relative; /* for arrow positioning */
background: #ffffcb;
border: 1px solid #c0c09a;
color: #666666;
border-radius: 1px;
font-family: 'HelveticaNeue','Helvetica',sans-serif;
font-size: 1em;
line-height: 1.3;
padding: 0.3em 0.5em;
z-index: 5;
}
.tooltip.white {
background: #fff;
border: none;
border-radius: .5em;
box-shadow: .1em .1em .6em #bbb;
}
`Version 1.0.3`
Shows a tooltip on mouseover and click. Hides on mouseout or blur.
The tooltip is an HTML element with `display:none`.
Positioned at the top by default, and offset to the left a little. If there is
no space on top, shown on bottom.
Tooltip colors, styles, and padding are controlled by your stylesheet.
The tooltip arrow is added and positioned automatically, and it matches the
background color and border color.
Requires browser support of `box-sizing:border-box` and `display:inline-block`.
Tooltip arrow is not present in IE versions 8 and below.
[Demo page](http://embed.plnkr.co/vV02YH/preview)
# Missing features:
- support for when a tooltip extends past the document body (cannot scroll to it)
# Improvements (unplanned):
1. More tooltip open/close detection (touch events)
2. Box-shadow support
# Changelog:
- 1.0.2 - Caret has correct border-width even with `border:none`
- 1.0.3 - Caret border element is completely hidden when border is 0.