I decided to build my own wikipedia-like tooltip.
Which works for all pages that are on the website itself - so not for external pages.
If you want full internet support for tooltip/rich links then you can use https://linkz.ai/ - I’ve used it, though it was too pricey for us since we have a ton of page-views. But it works brilliantly, nice and fast.
My own setup was build with a custom JS script in combination with Tippy.js:
You need to style your own classes as you wish for:
- tooltip-content-link
- tooltip-arrow
- tooltip-subtitle
- tooltip-heading
- tooltip-image
- tooltip-text"
Example page: Advanced mechanics | Advanced ⇀ Guide ★ Beyond All Reason RTS
You can hover over links and see the Tippy Tooltips popping up, with content from it’s meta-data.
Main benefits:
- No cost for uses/views/generations
- Can use all the meta-data from pages within your website
- Even uses url-stripping for additional title/category based on URL
- Fast
- Full control over design
Head Code:
<link
rel="stylesheet"
href="https://unpkg.com/tippy.js@6/animations/scale-subtle.css"
/>
Footer Code:
<!-- Include the Tippy.js library -->
<script src="https://unpkg.com/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
<script src="https://unpkg.com/tippy.js@6.3.3/dist/tippy-bundle.umd.min.js"></script>
<!-- HTML template for tooltip content -->
<div id="tooltip-template" style="display: none;">
<div>
<a href="" class="tooltip-content-link">
<div class="tooltip-arrow"></div>
<h5 class="tooltip-subtitle"></h5>
<h4 class="tooltip-heading"></h4>
<img src="" loading="lazy" alt="Image" class="tooltip-image">
<div class="tooltip-text"></div>
</a>
</div>
</div>
<!-- CSS styles for tooltip content -->
<style>
/* Custom arrow styles */
.tippy-arrow {
color: #050404;
}
/* Increase the z-index */
.tippy-box {
z-index: 99999;
}
</style>
<!-- JavaScript code -->
<script>
// Retrieve all divs with the class "rich-text" and "w-richtext"
const richtextDivs = document.querySelectorAll('.rich-text.w-richtext, .rich-text-unit-description.w-richtext, .rich-text-faq.w-richtext');
// Create a map to store the tippy instances
const tippyInstances = new Map();
// Fetch and prefetch the page metadata for each link
richtextDivs.forEach(div => {
const links = div.querySelectorAll('a');
links.forEach(link => {
const href = link.href;
// Exclude external links
if (!isInternalLink(href)) {
return;
}
// Prefetch the metadata for the link
fetchMetadata(href)
.then(metadata => {
// Get the tooltip template element
const template = document.getElementById('tooltip-template');
// Clone the template to create a new instance
const tooltipContent = template.cloneNode(true);
// Extract the first part of the URL after the domain name as the subtitle
const url = new URL(href);
const pathSegments = url.pathname.split('/').filter(segment => segment.trim() !== '');
const subtitle = pathSegments[0] || '';
// Remove " ⇀" from the title if it exists
const title = metadata.title;
const strippedTitle = title.split(' ⇀')[0];
// Remove "DPS 0" from the description if it exists
const description = metadata.description.replace(/.{3}DPS 0$/, '');
// Populate the tooltip content with metadata
tooltipContent.querySelector('.tooltip-subtitle').textContent = subtitle;
tooltipContent.querySelector('.tooltip-heading').textContent = strippedTitle;
tooltipContent.querySelector('.tooltip-text').innerHTML = description;
tooltipContent.querySelector('.tooltip-content-link').href = href; // Set the original link URL
// Set the ogImage source if it exists
const ogImage = tooltipContent.querySelector('.tooltip-image');
if (metadata.ogImage) {
ogImage.src = metadata.ogImage;
ogImage.style.display = 'block'; // Show the ogImage
} else {
ogImage.style.display = 'none'; // Hide the ogImage if not found
}
// Make the tooltip content visible
tooltipContent.style.display = 'block';
// Create the Tippy.js tooltip
const tippyInstance = tippy(link, {
content: tooltipContent.innerHTML,
allowHTML: true,
// maxWidth: 240, // Set the maximum width of the tooltip to 240
animation: 'scale-subtle',
arrow: false, // Disable the arrow
placement: 'top',
interactive: true,
trigger: 'mouseenter',
touch: false,
delay: [150, 250],
popperOptions: {
strategy: 'fixed',
modifiers: [
{
name: 'preventOverflow',
options: {
boundariesElement: document.body,
rootBoundary: 'document',
padding: 10,
},
},
],
},
});
// Store the tippy instance in the map
tippyInstances.set(link, tippyInstance);
})
.catch(error => {
console.error('Error prefetching metadata:', error);
});
});
});
// Fetches the page metadata using the link's URL
function fetchMetadata(url) {
return fetch(url)
.then(response => response.text())
.then(html => {
// Parse the HTML response to extract the metadata
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const title = doc.querySelector('title')?.innerText || '';
const ogImage = doc.querySelector('meta[property="og:image"]')?.content || '';
const description = doc.querySelector('meta[name="description"]')?.content || '';
// Return the metadata as an object
return { title, ogImage, description };
});
}
// Checks if a link is an internal link within the current domain
function isInternalLink(url) {
const currentOrigin = window.location.origin;
const linkOrigin = new URL(url).origin;
return linkOrigin === currentOrigin;
}
</script>