So this is a bit of a mix-up.
I want to highlight certain words and add a help-text to them when you hover them. My current code searches for keywords, let's say the word "Mammal". When it finds the word "Mammal" it adds a span element around the word with a class that styles it so that you get an underline on the word. It also adds a child span element on the word that is hidden and contains the help-text I want to show.
The child's position is set to position:absolute and placed directly underneath the underlined word. I have no control over the parent elements of the word or where on the page the word is, so the words parent/grandparent/etc might have overflow:hidden (and/or position:relative) which will partially hide the word. I want it to always show up (without taking any space on the page) when you hover the marked word, but I just can't think of a good way to solve this.
I've thought about putting the hover display text, not as a child of the hover element, but to have it further up above the overflow:hidden container, but I can't think of a good way how I would get that to work and how the child element would then target a grandparent sibling element to be displayed, etc.
Here's an element that shows how it looks and behaves:
.tooltip { border-bottom: 1px solid orange; }.tooltip.tooltipText { visibility: hidden; width: 100px; background-color: green; position: absolute; margin: 20px 0px 0px -80px; }.tooltip:hover.tooltipText { visibility: visible; }
<html> <body> <div style="overflow: hidden; width:100px;"> <div style="position:relative;"> <div>text <span class="tooltip">containing<span class="tooltipText">Hover text here</span></span> the word i'm looking for</div> </div> </div> </body>
First i get all the nodes, then i go through them updating the words and adding the required elements:
nodes.forEach(function(node){
let nextNode = node.nextSibling,
parent = node.parentNode,
content = node.textContent,
newNodes = [];
node.parentNode.removeChild(node);
content.split(wordToHighlight).forEach(function(part, i, arr){
newNodes.push(document.createTextNode(part));
if(i < arr.length - 1){
let highlight = document.createElement("span");
highlight.innerHTML = wordToHighlight;
highlight.classList.add('tooltip');
let label = document.createElement('span');
label.classList.add('tooltipText');
label.innerHTML = "Hover text here";
highlight.appendChild(label);
newNodes.push(highlight);
}
});
newNodes.forEach(function(n){
if(nextNode)
parent.insertBefore(n, nextNode);
else
parent.appendChild(n);
});
});
This logic works for the most part, but when a parent/grandparent/etc contains either position:relative or overflow:hidden then the text gets cut and I've tried fiddling with CSS for hours now to see if I could find a way to make it work without much success. I would also like a better way to center it directly underneath the word it highlights, but move it so that it won't go out of the viewport to the right/left/top/bottom depending on where the word appears if that's possible.
I can add more details/code or such if needed, thanks for any help!
just insert the tooltip outside the element which has overflow: hidden;
. That is the only solution here, though you will need to position accordingly in that case.
The comment from @pilchard about using the library Popper worked perfectly for this use case and it was easy and fast to set up.
function show(selector, popperInstance) {
const tooltip = document.querySelector(selector);
tooltip.setAttribute('data-show', '');
popperInstance.setOptions({
modifiers: [{ name: 'eventListeners', enabled: true }],
});
popperInstance.update();
}
function hide(selector, popperInstance) {
const tooltip = document.querySelector(selector);
tooltip.removeAttribute('data-show');
popperInstance.setOptions({
modifiers: [{ name: 'eventListeners', enabled: false }],
});
}
function underlineWords()
{
const showEvents = ['mouseenter', 'focus'];
const hideEvents = ['mouseleave', 'blur'];
let nodeNumber = 1;
nodes.forEach(function(node){
let nextNode = node.nextSibling,
parent = node.parentNode,
content = node.textContent,
newNodes = [],
selectorIds = [],
poppers = [],
textIds = [];
node.parentNode.removeChild(node);
content.split(itemName).forEach(function(part, i, arr){
newNodes.push(document.createTextNode(part));
if(i < arr.length - 1){
let highlight = document.createElement('span');
highlight.innerHTML = itemName;
highlight.classList.add('tooltipHover');
highlight.id = itemName + nodeNumber + '-' + i + 'Hover';
highlight.setAttribute('aria-describedby', 'tooltip');
selectorIds.push('#' + highlight.id);
let tooltipText = document.createElement('span');
tooltipText.innerHTML = "Hover text here";
tooltipText.classList.add('tooltip');
tooltipText.id = nodeNumber + '-' + i + 'Show';
textIds.push('#' + tooltipText.id);
highlight.appendChild(tooltipText);
newNodes.push(highlight);
const popper = Popper.createPopper(highlight, tooltipText, {
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8],
},
},
],
});
poppers.push(popper);
}
});
newNodes.forEach(function(n){
if(nextNode)
parent.insertBefore(n, nextNode);
else
parent.appendChild(n);
});
for(let i = 0; i < selectorIds.length; i++){
const popper = poppers[i];
const selectorId = selectorIds[i];
const textId = textIds[i];
const selector = document.querySelector(selectorId);
showEvents.forEach(event => {
selector.addEventListener(event, function(){ show(textId, popper) });
});
hideEvents.forEach(event => {
selector.addEventListener(event, function(){ hide(textId, popper) });
});
}
nodeNumber += 1;
});
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.