Custom Tooltip Web Component
by Keith Rowles • 28/10/2023JavaScript
Summary
Created a custom tooltip web component using vanilla javascript, shadow dom, shadowroot, templates and slots.
HTML
Add the html.
<div class="container">
<p>
<custom-tooltip text="This is a custom tooltip web component!">
<span class="highlight">Tooltip Web Component</span>
</custom-tooltip>
</p>
</div>
CSS
Add some css to the html document.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-family: Arial, Helvetica, sans-serif;
}
:root {
--color-primary: lightblue;
--color-background: aliceblue;
}
.container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
background-color: var(--color-background);
margin: 0;
padding: 0;
}
.highlight {
background-color: yellow;
}
JavaScript
Add the custom javascript component.
class Tooltip extends HTMLElement {
constructor() {
super();
// this._tooltipContainer;
this._tooltipIcon;
this._tooltipVisible = false;
this._tooltipText = 'No Tool Tip Has Been Set';
this.attachShadow({
mode: 'open',
});
this.shadowRoot.innerHTML = `
<style>
div {
background-color: black;
color: white;
position: absolute;
z-index: 10;
padding: 0.7rem;
border-radius: 5px;
top: 2rem;
left: 1rem;
font-weight: normal;
line-height: 150%;
}
::slotted(.highlight) {
border-bottom: 1px dotted red;
}
.icon {
background: black;
color: white;
padding: 0.5rem;
border-radius: 50%
}
:host {
position: relative;
}
:host-context(p) {
font-weight: bold;
}
</style>
<slot>Default Text</slot>
<span class="icon"> (?)</span>
`;
}
connectedCallback() {
if (this.hasAttribute('text')) {
this._tooltipText = this.getAttribute('text');
}
this._tooltipIcon = this.shadowRoot.querySelector('span');
this._tooltipIcon.addEventListener(
'mouseenter',
this._showTooltip.bind(this)
);
this._tooltipIcon.addEventListener(
'mouseleave',
this._hideTooltip.bind(this)
);
this._render();
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) {
return;
}
if (name === 'text') {
this._tooltipText = newValue;
}
}
static get observedAttributes() {
return ['text'];
}
disconnectedCallback() {
// these are technically redundant because event listeners are done by browser - just here for demo if you need clean up
this._tooltipIcon.removeEventListener('mouseenter', this._showTooltip);
this._tooltipIcon.removeEventListener('mouseleave', this._hideTooltip);
}
_render() {
let tooltipContainer = this.shadowRoot.querySelector('div');
if (this._tooltipVisible) {
tooltipContainer = document.createElement('div');
tooltipContainer.textContent = this._tooltipText;
this.shadowRoot.appendChild(tooltipContainer);
} else {
if (tooltipContainer) {
this.shadowRoot.removeChild(tooltipContainer);
}
}
}
_showTooltip() {
this._tooltipVisible = true;
this._render();
}
_hideTooltip() {
this._tooltipVisible = false;
this._render();
}
}
customElements.define('custom-tooltip', Tooltip);
Demo
Open demo on JS Fiddle.
Link to Demo