简体   繁体   中英

Css display dynamically added child with position absolute over parent with overflow hidden

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM