In terms of performance, what is the best way to bind mouseover/mouseout event on dynamically created elements?
This is what I have but I'm not sure it is ok for performance:
document.addEventListener('mouseover', (e) => {
if (e.target.getAttribute('data-tooltip')) {
// Show
}
});
document.addEventListener('mouseout', (e) => {
if (e.target.getAttribute('data-tooltip')) {
// Hide
}
});
Maybe something like debouncing should be added or more optimized way exists?
This is how I would bind events for non-dynamic elements:
document.querySelectorAll('[data-tooltip]').forEach(item => {
item.addEventListener('mouseover', this.show);
item.addEventListener('mouseout', this.hide);
});
Please note that I'm using Vanilla JavaScript and no libraries.
This is the markup that can trigger a tooltip over the element:
<span data-tooltip="top" title="Tooltip text here.">Tooltips</span>
The show()
and hide()
class methods generate a div
with text from the tooltip title attribute. I know there are many CSS only methods to create a tooltip, but they use data attributes, which lack the a11y part.
My intention is to be able to show default title attributes for people without JavaScript and show fancy tooltip to people with enabled JS and temporary "empty" the title attribute while mouseovered to prevent default browser behavior.
// Tooltips
class Tooltips {
// Init and Bind events dinamically
static init() {
document.addEventListener('mouseover', (e) => {
if (e.target.getAttribute('data-tooltip')) {
this.show(e.target);
}
});
document.addEventListener('mouseout', (e) => {
if (e.target.getAttribute('data-tooltip')) {
this.hide(e.target);
}
});
}
// Show
static show(item) {
this._lastActive = 'tooltip-'+ this.toHex(item.getAttribute('title'));
this._tempTitlte = item.getAttribute('title');
item.setAttribute('title', '');
// If tooltip exists, just show it
if (document.getElementById(this._lastActive)) {
document.getElementById(this._lastActive).setAttribute('aria-hidden', 'false');
return true;
}
// Create the tooltip
const tooltip = document.createElement('div');
tooltip.setAttribute('class', 'site-tooltip site-tooltip-'+ item.getAttribute('data-tooltip'));
tooltip.setAttribute('id', this._lastActive);
tooltip.innerHTML = this._tempTitlte;
tooltip.setAttribute('aria-hidden', 'false');
document.body.appendChild(tooltip);
// Position it
const top = item.offsetTop - tooltip.offsetHeight - 10; // 10px for tooltip arrow
const left = item.offsetLeft + item.offsetWidth / 2 - tooltip.offsetWidth / 2;
tooltip.setAttribute('style', 'left: '+ left +'px; top: '+ top +'px;');
}
// Hide
static hide(item) {
document.getElementById(this._lastActive).setAttribute('aria-hidden', 'true');
item.setAttribute('title', this._tempTitlte);
}
// string to hex
static toHex(str) {
var result = '';
for (var i = 0; i < str.length; i++) {
result += str.charCodeAt(i).toString(16);
}
return result;
}
}
// Document ready
document.addEventListener('DOMContentLoaded', function() {
// Let us create an element
const tooltip = document.createElement('button');
tooltip.setAttribute('data-tooltip', 'top');
tooltip.setAttribute('title', 'Fancy tooltip text');
tooltip.innerHTML = 'Show Tooltip';
document.body.appendChild(tooltip);
// Init tooltips
Tooltips.init();
});
.site-tooltip{
position: absolute;
padding: 7px 10px 9px;
font-size: 14px;
line-height: 17px;
background-color: #000;
color: #fff;
border-radius: 3px;
display: none;
z-index: 99;
max-width: 250px;
box-sizing: border-box;
}
.site-tooltip:after{
content: "";
display: block;
z-index: 98;
width: 14px;
height: 14px;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
position: absolute;
background-color: #000;
border-radius: 3px;
}
[data-tooltip] {
cursor: help;
}
.site-tooltip-top:after{left: 50%; bottom: -5px; margin-left: -7px;}
.site-tooltip[aria-hidden="false"]{
display: block;
}
<br/>
<br/>
<br/>
<br/>
Some spacing
<br/>
<br/>
<br/>
<br/>
<br/>
mouseenter/mouseleave
instead? That should give you best performance overall. \$\endgroup\$ – user10711 Jan 12 '17 at 19:47