feat: Dynamic tooltips

This commit is contained in:
Toerd@archlinux 2020-05-25 19:14:04 +02:00
commit 25cd0081d0

178
dynamic_tooltips.js Normal file
View File

@ -0,0 +1,178 @@
// Alias für die Queryselector Methoden
let $$ = document.querySelector.bind(document);
let $$$ = document.querySelectorAll.bind(document);
// Array für momentan angezeigte/aktive Tooltips
let visibleTooltips = [];
/**
* Dieses Script funktioniert wie folgt:
* Einem beliebigen HTML Element kann ein Attribut mit dem Namen `data-tooltip`
* hinzugefügt werden. Dieses kann folgendes enthalten:
* 1. Den Tooltip Text selbst: data-tooltip='Mein Tooltip Text hier'
* 2. Eine ID eines anderen Elements: data-tooltip='#mein-tooltip-elem'
* Der `#` ist hierbei wichtig!
* Methode 2 kann z.B. für etwas komplexere Tooltips verwendet werden
*
* Die Styles von Tooltips findet man in _layout.scss (.ttip, .ttip-triangle)
*/
document.addEventListener("mouseover", e => {
let t = e.target;
if (typeof t.dataset.tooltip !== "undefined") {
// Mehrfaches erstellen bei hinundher zwischen Tooltip und Caller vermeiden
removeTooltips();
let ttip = positionTooltip(t);
// Nicht sicher ob das tatsächlich die eleganteste Variante ist.
// Man könnte auch ein unsichtbares Element verwenden, das die Lücke
// zwischen Caller und Tooltip ausfüllt um ein "Übergehen" auf das
// Tooltip zu ermöglichen.
// Hier hat man 500ms Zeit um das zu tun.
let oneshot = e => {
setTimeout((e) => {
let onTooltip = false;
$$$(":hover").forEach(el => {
if (el.className.includes("ttip")) {
onTooltip = true;
return;
}
if (typeof el.dataset.tooltip !== "undefined") {
onTooltip = true;
return;
}
});
if (!onTooltip) {
removeTooltips();
t.removeEventListener("mouseleave", oneshot);
}
}, 500);
};
t.addEventListener("mouseleave", oneshot);
ttip.addEventListener("mouseleave", e => {
removeTooltips();
});
}
});
/**
* Positioniert ein Tooltip abhänging von der Position von `caller` im Viewport
* und erzeugt ein kleines Dreieck, dass auf den `caller` zeigt.
* @param {object} caller Ein beliebiges Element mit einem `data` feld data-tooltip
* @return {object}
*/
function positionTooltip(caller) {
// Computed Style des Callers in Bezug auf den Viewport
let cBR = caller.getBoundingClientRect();
// Tooltip anfragen
let tooltip = getTooltip(caller);
// Farbe des kleinen Tooltip Dreiecks
let triColor = "white";
let triSize = 5;
// Dreieck für Tooltip erstellen
let tri = document.createElement("div");
tri.classList = "ttip-triangle";
tri.style.borderLeft = triSize + "px solid transparent";
tri.style.borderRight = triSize + "px solid transparent";
// Bestimmen ob simples Tooltip mit Text aus data-tooltip
// oder komplexeres Tooltip aus Element mit ID XYZ angefordert wird
if (typeof tooltip === "string") {
let text = tooltip;
tooltip = document.createElement("div");
// Element zum Löschen mit Klasse del-ttip ausstatten (func removeTooltips)
tooltip.classList = "ttip del-ttip";
// Tooltip mit Text befüllen
tooltip.innerHTML = text;
document.body.appendChild(tooltip);
} else {
tooltip.style.display = "block";
tooltip.classList = "ttip";
}
// Dreieck in Tooltipfenster einfügen
tooltip.appendChild(tri);
// Position des Anfordenden Elements
let hPos = cBR.left + cBR.width / 2;
let vPos = cBR.top + cBR.height / 2;
// Mittelpunkt
let hMid = window.innerWidth / 2;
let vMid = window.innerHeight / 2;
// Position des Tooltips
let ttop;
let tleft;
// Position des Dreiecks
let mtop;
tooltip.style.position = "fixed";
let tBR = tooltip.getBoundingClientRect();
let triBR = tri.getBoundingClientRect();
// Die Position des "Tooltip-Callers" in diesem Schema:
/*
-----------------
| | |
| o | |
| | o |
-----------------
| | o |
| | |
|o | |
-----------------
*/
// bestimmt die Positionierung des Tooltips und des kleinen Dreiecks
// Links
if (hPos <= hMid) {
tleft = cBR.left;
// Dreieck positionieren
tri.style.left = cBR.width / 2 - triSize + "px";
// Rechts
} else {
tleft = cBR.right - tBR.width;
// Dreieck positionieren
tri.style.right = cBR.width / 2 - triSize + "px";
}
// Oben
if (vPos <= vMid) {
// Tooltip Abstand zum Caller
ttop = cBR.bottom + triSize;
// Dreieck positionieren
tri.style.borderBottom = triSize + "px solid " + triColor;
tri.style.top = -triSize + "px";
// Unten
} else {
// Tooltip Abstand zum Caller
ttop = cBR.top - tBR.height - triSize;
// Dreieck positioniere
tri.style.borderTop = triSize + "px solid " + triColor;
tri.style.bottom = -triSize + "px";
}
// Tooltip
tooltip.style.top = ttop + "px";
tooltip.style.left = tleft + "px";
// Zum Array der momentan angezeigten Tooltips hinzufügen um später zu entfernen
visibleTooltips.push(tooltip);
return tooltip;
}
function getTooltip(element) {
let tt = element.dataset.tooltip;
// Entweder das Tooltip Element oder den Tooltip Text zurückgeben
return (/^#.+$/.test(tt)) ? $$(tt) : tt;
}
function removeTooltips() {
// Tooltips ausblenden/entfernen, je nach dem ob dynamisch erzeugt oder nicht
visibleTooltips.forEach(ele => {
if (ele.className.includes("del-ttip")) {
document.body.removeChild(ele);
} else {
ele.style.display = "none";
// Hinzugefügtes Dreieck entfernen
ele.removeChild(ele.lastChild);
}
});
// Array leeren, da alle Tooltips weg sein sollten
visibleTooltips = [];
}