<div style="display: flex; justify-content: space-between; width:500px; margin: 100px auto;">
<p>Simple <span style="color: red" data-tooltip_01-content="Message">[hover]</span></p>
<p>HTML1 <span style="color: red" data-tooltip_01-content="#content">[hover]</span></p>
<p>HTML2 <span style="color: red" data-tooltip_01-content="#content">[hover]</span></p>
</div>
<div id="content" class="tooltip_01--content">
<p><b>Title</b></p>
<ul>
<li><a href="#">Contents</a></li>
<li><a href="#">Contents</a></li>
<li><a href="#">Contents</a></li>
</ul>
</div>
/* No context defined. */
// =====================================================================================
//
// tooltip_01
//
// =====================================================================================
@use 'scss/global' as g;
.tooltip_01--content {
position: absolute;
top: 0;
left: 0;
display: none;
padding: 8px 16px;
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15);
&.is_ready {
display: inline-block;
}
&-top {
&::before {
position: absolute;
bottom: -16px;
left: 50%;
content: '';
border: 8px solid transparent;
border-top: 8px solid #ddd;
transform: translateX(-50%);
}
&::after {
position: absolute;
bottom: -12px;
left: 50%;
content: '';
border: 6px solid transparent;
border-top: 6px solid #fff;
transform: translateX(-50%);
}
}
&-bottom {
&::before {
position: absolute;
top: -16px;
left: 50%;
content: '';
border: 8px solid transparent;
border-bottom: 8px solid #ddd;
transform: translateX(-50%);
}
&::after {
position: absolute;
top: -12px;
left: 50%;
content: '';
border: 6px solid transparent;
border-bottom: 6px solid #fff;
transform: translateX(-50%);
}
}
&-left {
&::before {
position: absolute;
top: 50%;
right: -8px;
content: '';
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-left: 8px solid #ddd;
transform: translateY(-50%);
}
&::after {
position: absolute;
top: 50%;
right: -6px;
content: '';
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-left: 6px solid #fff;
transform: translateY(-50%);
}
}
&-right {
&::before {
position: absolute;
top: 50%;
left: -8px;
content: '';
border-top: 8px solid transparent;
border-right: 8px solid #ddd;
border-bottom: 8px solid transparent;
transform: translateY(-50%);
}
&::after {
position: absolute;
top: 50%;
left: -6px;
content: '';
border-top: 6px solid transparent;
border-right: 6px solid #fff;
border-bottom: 6px solid transparent;
transform: translateY(-50%);
}
}
}
@keyframes tooltip_01_top {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes tooltip_01_bottom {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes tooltip_01_left {
from {
opacity: 0;
transform: translateX(5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes tooltip_01_right {
from {
opacity: 0;
transform: translateX(-5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.tooltip_01-show_anim_top {
animation: tooltip_01_top 0.2s ease-out;
}
.tooltip_01-hide_anim_top {
animation: tooltip_01_top 0.1s ease-in reverse;
}
.tooltip_01-show_anim_bottom {
animation: tooltip_01_bottom 0.2s ease-out;
}
.tooltip_01-hide_anim_bottom {
animation: tooltip_01_bottom 0.1s ease-in reverse;
}
.tooltip_01-show_anim_left {
animation: tooltip_01_left 0.2s ease-out;
}
.tooltip_01-hide_anim_left {
animation: tooltip_01_left 0.1s ease-in reverse;
}
.tooltip_01-show_anim_right {
animation: tooltip_01_right 0.2s ease-out;
}
.tooltip_01-hide_anim_right {
animation: tooltip_01_right 0.1s ease-in reverse;
}
// =====================================================================================
//
// tooltip_01
// - このファイルを「src/js/components」に移動して使用してください
// - 同種のコンポーネントを併用する場合は、取り込むか、新しいRootsにするなどしてください
//
// TODO: スクリプトは信頼性のあるライブラリ(候補:Tippy.js)をベースにする
//
// =====================================================================================
import $ from 'jquery';
class Rect {
constructor(top, left, width, height) {
this.top = top;
this.left = left;
this.width = width;
this.height = height;
this.bottom = this.top + this.height;
this.right = this.left + this.width;
}
inView() {
const $window = $(window);
const scrollTop = $window.scrollTop();
const scrollBottom = scrollTop + $window.height();
const scrollLeft = $window.scrollLeft();
const scrollRight = scrollLeft + $window.width();
if (scrollBottom > this.top + this.height && scrollTop < this.top) {
if (scrollRight > this.left + this.width && scrollLeft < this.left) {
return true;
}
}
return false;
}
intersect(top, left) {
return (
top >= this.top &&
top <= this.top + this.height &&
left >= this.left &&
left <= this.left + this.width
);
}
}
export class Tooltip {
static DEBUG_MODE = false;
static MARGIN = 10;
static PADDING = 3;
constructor(rootsName, suffixNum, targetElement) {
console.log(rootsName);
this.rootsName = rootsName;
this.suffixNum = suffixNum;
this.id = rootsName + '_' + this.suffixNum;
this.target = targetElement;
this.content = null;
this.$body = $('body');
this.$target = $(targetElement);
this.$content = null;
this.targetRect = null;
this.contentRect = null;
this.contentPosition = '';
if (Tooltip.DEBUG_MODE) {
this.$c = $('<div></div>');
this.$t = $('<div></div>');
}
this.content = this.$target.attr('data-' + this.rootsName + '-content');
if (this.content.substr(0, 1) === '#') {
this.$content = $(this.content).clone();
} else {
this.$content = $(
'<div class="' +
this.rootsName +
'--content">' +
this.content +
'</div>'
);
}
this.$content.addClass('is_ready');
this.$target.on('mouseenter.' + this.rootsName, () => {
this.show();
});
}
get element() {
return this.target;
}
capitalize(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
setPositions() {
const contentWidth = this.$content.outerWidth();
const contentHeight = this.$content.outerHeight();
const targetWidth = this.$target.outerWidth();
const targetHeight = this.$target.outerHeight();
const targetOffsetTop = this.$target.offset().top;
const targetOffsetLeft = this.$target.offset().left;
const contentRectTop = new Rect(
targetOffsetTop - contentHeight - Tooltip.MARGIN,
contentWidth > targetWidth
? targetOffsetLeft + targetWidth / 2 - contentWidth / 2
: targetOffsetLeft,
Math.max(contentWidth, targetWidth),
contentHeight + Tooltip.MARGIN
);
const contentRectBottom = new Rect(
targetOffsetTop + targetHeight,
contentRectTop.left,
contentRectTop.width,
contentRectTop.height
);
const contentRectLeft = new Rect(
contentHeight > targetHeight
? targetOffsetTop + targetHeight / 2 - contentHeight / 2
: targetOffsetTop,
targetOffsetLeft - contentWidth - Tooltip.MARGIN,
contentRectTop.width + Tooltip.MARGIN,
Math.max(contentHeight, targetHeight)
);
const contentRectRight = new Rect(
contentRectLeft.top,
targetOffsetLeft + targetWidth,
contentRectLeft.width,
contentRectLeft.height
);
if (contentRectTop.inView()) {
this.contentPosition = 'top';
this.contentRect = contentRectTop;
} else if (contentRectBottom.inView()) {
this.contentPosition = 'bottom';
this.contentRect = contentRectBottom;
} else if (contentRectRight.inView()) {
this.contentPosition = 'right';
this.contentRect = contentRectRight;
} else if (contentRectLeft.inView()) {
this.contentPosition = 'left';
this.contentRect = contentRectLeft;
} else {
this.contentPosition = 'top';
this.contentRect = contentRectTop;
}
this.targetRect = new Rect(
targetOffsetTop - Tooltip.PADDING,
targetOffsetLeft - Tooltip.PADDING,
targetWidth + Tooltip.PADDING * 2,
targetHeight + Tooltip.PADDING * 2
);
this.$content
.css({
top:
this.contentPosition === 'bottom'
? this.contentRect.top + Tooltip.MARGIN
: this.contentRect.top,
left:
this.contentPosition === 'right'
? this.contentRect.left + Tooltip.MARGIN
: this.contentRect.left,
})
.addClass(this.rootsName + '--content-' + this.contentPosition);
if (Tooltip.DEBUG_MODE) {
this.$t
.css({
position: 'absolute',
top: this.targetRect.top,
left: this.targetRect.left,
width: this.targetRect.width,
height: this.targetRect.height,
background: 'rgba(0,0,0,.4)',
pointerEvents: 'none',
})
.appendTo(this.$body);
this.$c
.css({
position: 'absolute',
top: this.contentRect.top,
left: this.contentRect.left,
width: this.contentRect.width,
height: this.contentRect.height,
background: 'rgba(0,0,0,.4)',
pointerEvents: 'none',
})
.appendTo(this.$body);
}
}
show() {
this.$content.appendTo(this.$body);
this.$target.attr('aria-describedby', this.id).off('mouseenter.tooltip');
this.setPositions();
this.$content
.on('animationend.' + this.rootsName, () => {
this.$content
.removeClass(this.rootsName + '-show_anim_' + this.contentPosition)
.off('animationend.' + this.rootsName);
})
.attr('id', this.id)
.addClass(this.rootsName + '-show_anim_' + this.contentPosition);
this.$body.on('mousemove.' + this.id, (e) => {
if (this.contentRect.intersect(e.pageY, e.pageX)) {
return;
}
if (this.targetRect.intersect(e.pageY, e.pageX)) {
return;
}
this.$body.off('mousemove.' + this.id);
this.hide();
});
}
hide() {
this.$target.removeAttr('aria-describedby');
this.$content
.on('animationend.tooltip', () => {
this.$content
.removeClass(this.rootsName + '--content-' + this.contentPosition)
.removeClass(this.rootsName + '-hide_anim_' + this.contentPosition)
.removeClass('is_show')
.attr('id', null)
.off('animationend.tooltip')
.remove();
})
.addClass(this.rootsName + '-hide_anim_' + this.contentPosition);
this.$target.on('mouseenter.' + this.rootsName, (e) => {
this.show();
});
if (Tooltip.DEBUG_MODE) {
this.$t.remove();
this.$c.remove();
}
}
}
export default function (rootsName) {
$(() => {
Tooltip.DEBUG_MODE = false;
$('[data-' + rootsName + '-content]').each((index, element) => {
new Tooltip(rootsName, index, element);
});
});
}