Code
<!DOCTYPE html>
<html lang="ka">
<head>
<meta charset="UTF-8" />
<title>Custom Tooltip</title>
<style>
.tooltip {
position: absolute;
background-color: #000;
color: #fff;
padding: 4px 6px;
border: solid 2px red;
z-index: 10000;
pointer-events: none;
white-space: nowrap;
visibility: hidden;
will-change: top, left;
font-size: 13px;
}
.tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border-width: 8px;
border-style: solid;
border-color: red transparent transparent transparent;
}
</style>
</head>
<body>
<button title="ეს არის tooltip">Hover me</button>
<script>
document.addEventListener('DOMContentLoaded', () => {
const elementsWithTitle = [...document.querySelectorAll('[title]')];
let tooltip = null;
let tooltipTarget = null;
let disconnectOnRemove = null;
function preventDefault(e) {
e.preventDefault();
}
function preventScrollKeys(e) {
const keys = [' ', 'PageUp', 'PageDown', 'End', 'Home', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown'];
if (keys.includes(e.key)) {
e.preventDefault();
}
}
function disableScroll() {
document.addEventListener('wheel', preventDefault, { passive: false });
document.addEventListener('touchmove', preventDefault, { passive: false });
document.addEventListener('keydown', preventScrollKeys);
}
function enableScroll() {
document.removeEventListener('wheel', preventDefault, { passive: false });
document.removeEventListener('touchmove', preventDefault, { passive: false });
document.removeEventListener('keydown', preventScrollKeys);
}
function createTooltip(element) {
if (tooltip) return;
tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.textContent = element.dataset.tooltipTitle;
document.body.appendChild(tooltip);
tooltipTarget = element;
disableScroll();
const removalObserver = new MutationObserver(() => {
if (!document.body.contains(tooltipTarget)) {
destroyTooltip();
}
});
removalObserver.observe(document.body, {
childList: true,
subtree: true
});
disconnectOnRemove = () => removalObserver.disconnect();
requestAnimationFrame(() => {
const rect = element.getBoundingClientRect();
const x =
rect.left +
window.pageXOffset +
rect.width / 2 -
tooltip.offsetWidth / 2;
const y =
rect.top +
window.pageYOffset -
tooltip.offsetHeight -
7;
tooltip.style.left = `${x}px`;
tooltip.style.top = `${y}px`;
tooltip.style.visibility = 'visible';
});
}
function destroyTooltip() {
if (tooltip) {
tooltip.remove();
tooltip = null;
tooltipTarget = null;
}
enableScroll();
if (disconnectOnRemove) {
disconnectOnRemove();
disconnectOnRemove = null;
}
}
function enableTooltipForElement(element) {
if (element.dataset.tooltipTitle) return;
const titleText = element.getAttribute('title');
if (!titleText) return;
element.dataset.tooltipTitle = titleText;
element.removeAttribute('title');
element.addEventListener('mouseenter', () => createTooltip(element));
element.addEventListener('mouseleave', destroyTooltip);
}
elementsWithTitle.forEach(enableTooltipForElement);
['scroll', 'resize', 'click'].forEach(evt => {
window.addEventListener(evt, destroyTooltip, { passive: true });
});
});
</script>
</body>
</html>