JAVASCRIPT
Building a Custom v-tooltip Directive in Vue 3
Discover how to extend Vue 3's capabilities by creating a custom directive for interactive tooltips, enhancing user experience and adding reusable behavior to any DOM element.
// 1. Define the custom directive (e.g., in directives/vTooltip.js)
import { createApp, h } from 'vue';
const tooltipAppMap = new WeakMap();
const vTooltip = {
mounted(el, binding) {
const text = binding.value || el.getAttribute('title'); // Use v-tooltip value or existing title
if (!text) return;
el.removeAttribute('title'); // Prevent native tooltip
const tooltipContainer = document.createElement('div');
tooltipContainer.className = 'v-tooltip-container';
tooltipContainer.style.position = 'absolute';
tooltipContainer.style.pointerEvents = 'none';
tooltipContainer.style.zIndex = '10000';
tooltipContainer.style.opacity = '0';
tooltipContainer.style.transition = 'opacity 0.3s ease-in-out';
const tooltipContent = h('div', { class: 'v-tooltip-content' }, text);
const app = createApp({ render: () => tooltipContent });
app.mount(tooltipContainer);
document.body.appendChild(tooltipContainer);
tooltipAppMap.set(el, { app, tooltipContainer });
const showTooltip = () => {
const { top, left, width, height } = el.getBoundingClientRect();
tooltipContainer.style.top = `${top + window.scrollY + height + 5}px`;
tooltipContainer.style.left = `${left + window.scrollX + (width / 2) - (tooltipContainer.offsetWidth / 2)}px`;
tooltipContainer.style.opacity = '1';
};
const hideTooltip = () => {
tooltipContainer.style.opacity = '0';
};
el.addEventListener('mouseenter', showTooltip);
el.addEventListener('mouseleave', hideTooltip);
el._vTooltip_events = { showTooltip, hideTooltip }; // Store references for cleanup
},
unmounted(el) {
const { app, tooltipContainer } = tooltipAppMap.get(el);
if (app && tooltipContainer) {
app.unmount();
document.body.removeChild(tooltipContainer);
tooltipAppMap.delete(el);
}
if (el._vTooltip_events) {
el.removeEventListener('mouseenter', el._vTooltip_events.showTooltip);
el.removeEventListener('mouseleave', el._vTooltip_events.hideTooltip);
delete el._vTooltip_events;
}
}
};
export default vTooltip;
// 2. Register the directive globally (e.g., in main.js)
// import { createApp } from 'vue';
// import App from './App.vue';
// import vTooltip from './directives/vTooltip';
// const app = createApp(App);
// app.directive('tooltip', vTooltip);
// app.mount('#app');
// 3. Use the directive in a component
/*
<template>
<button v-tooltip="'This is a helpful tooltip!'">
Hover me for info
</button>
<span v-tooltip.top="'Another tooltip with specific placement (advanced logic needed)'" style="margin-left: 20px;">
Hover this text
</span>
<p v-tooltip.bottom.right="'More tooltip options'" style="margin-left: 40px;">
Paragraph with tooltip
</p>
</template>
<script setup>
// No script needed for simple directive usage
</script>
<style>
.v-tooltip-content {
background-color: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
white-space: nowrap;
font-size: 0.85em;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
</style>
*/
How it works: This snippet demonstrates creating a custom `v-tooltip` directive in Vue 3. It leverages directive lifecycle hooks (`mounted`, `unmounted`) to attach and remove event listeners, dynamically create a tooltip element, and position it relative to the bound element. It uses `createApp` and `h` from Vue to render the tooltip's content as a small Vue application, ensuring full reactivity and proper cleanup when the element is removed from the DOM. This pattern allows for reusable and declarative UI enhancements.