Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allow element's after and before content to override parent overflow restriction

I have a div whose overflow property is set to scroll in order to see all the contained fields without taking too much of the page's space. Each field has a span (the field's title) and input associated. I offer the possibility to the users to hover on the spans and see some useful information as a tool tip. I use the spans after and before pseudo-elements on the spans hover in order to add a customized tool tip. However, the tool tips display is restricted by the parent div overflow restriction.

Here is an example of the rendered HTML:

<div id="ContentPlaceHolder1_leftDiv" class="l-custom-left">     
    <div class="personalizedFields">
        <table>
            <tr>
                <td>
                    <span title="Champ associé: Prenom" class="tooltip">Prénom</span>
                </td>
            </tr>
            <tr>
                <td>
                    <input name="ctl00$ContentPlaceHolder1$ucPF$ucCustomField36$field36" type="text" id="field36" />

                </td>
            </tr>

            <tr>
                <td>
                    <span title="Champ associé: Nom" class="tooltip">Nom</span>
                </td>
            </tr>
            <tr>
                <td>
                    <input name="ctl00$ContentPlaceHolder1$ucPF$ucCustomField37$field37" type="text" id="field37" />

                </td>
            </tr>
        </table>
    </div>
</div>

The css of the parent div and spans:

.l-custom-left
{
    overflow-x: hidden;
    width: 250px;
    height: 50vh;
}

.tooltip:hover:after{
    background: #333;
    background: rgba(0,0,0,.8);
    border-radius: 5px;
    bottom: 26px;
    color: #fff;
    content: attr(title);
    left: 20%;
    padding: 5px 10px;
    position: absolute;
    z-index: 98;
}

.tooltip:hover:before{
    border: solid;
    border-color: #333 transparent;
    border-width: 6px 6px 0 6px;
    bottom: 20px;
    content: "";
    left: 50%;
    position: absolute;
    z-index: 99;
}

How can I allow my spans after and before content to "override" the div parent overflow restriction?

JSfiddle

like image 778
actaram Avatar asked Jun 30 '15 16:06

actaram


2 Answers

If you really want this to work, there are two ways. The first is to use JavaScript (which I won't go into) to place an element outside the container at the position of the element. It's an obnoxious fix, and I've had to implement it too many times to count prior to the advent of CSS3.

The second is much more elegant, but does break semantics for HTML. The reason that your :before and :after pseudos won't pop out of a scrolling container is that they're treated like children of the .tooltip element, and thus part of the document flow for the scrolling container even if they're positioned absolutely.

So how do we handle this? We cheat, plain and simple.

Add a second element after your span like so:

<span class="tooltip">Prénom</span>
<span title="Champ associé: Prenom" class="tooltip-hovershim">Prénom</span>

We keep the same content so that the element will be the same size.

Then, alter your CSS like this:

.tooltip, .tooltip-hovershim
{
    display: inline;
}

.tooltip:hover:after, .tooltip-hovershim:hover:after{
    background: #333;
    background: rgba(0,0,0,.8);
    border-radius: 5px;
    bottom: 26px;
    color: #fff;
    content: attr(title);
    left: 20%;
    padding: 5px 10px;
    position: absolute;
    z-index: 98;
}
.tooltip-hovershim { 
    position: absolute;
    transform: translateX(-100%);
    color: transparent;
}

.tooltip:hover:before, .tooltip-hovershim:hover:before {
    border: solid;
    border-color: #333 transparent;
    border-width: 6px 6px 0 6px;
    bottom: 20px;
    content: "";
    left: 50%;
    position: absolute;
    z-index: 99;
}

Et voila! Vous avez votre élément flottant. It's that simple.

JSFiddle Example

Only applied to the first two elements for brevity's sake.


Just for shits and giggles, here's the Javascript variant

JSFiddle with the JS variant

And here's the crucial portion of Javascript:

var span_labels = document.querySelectorAll('.personalizedFields td span'),
    label_house = document.createElement('div');

document.body.appendChild(label_house);
label_house.setAttribute('class', 'tooltip-hoverer');

for (var i=0,l=span_labels.length;i<l;i++){
  var curr_label = span_labels[i];
    curr_label.addEventListener('mouseover', function(e) {
       e.preventDefault();
       label_house.innerHTML = e.target.getAttribute('title');
       var xy = getOffset(e.target);
       label_house.style.top = (xy.top - 26) + 'px';
       label_house.style.left = (xy.left) + 'px';
       label_house.style.display = 'block';
    });
    curr_label.addEventListener('mouseout', function() {
       label_house.style.display = 'none'; 
    });
}
                                
function getOffset(el) {
    var elementRectangle = el.getBoundingClientRect();
    var _x = elementRectangle.left;
    var _y = elementRectangle.top + el.offsetParent.scrollTop;
    return { top: _y, left: _x };
}
like image 56
Josh Burgess Avatar answered Nov 14 '22 23:11

Josh Burgess


Since what I wanted to do isn't feasible with only CSS, I decided to go with a mix of JavaScript and JQuery, inspired by Josh Burgess's JS solution. So if you were looking for a more JQuery oriented solution, here it is:

var tooltip;

$(document).ready(function() {
    $('span.tooltip').on({
        mouseenter: function (e) {
            tooltip = document.createElement('div');
            document.body.appendChild(tooltip);
            $(tooltip).addClass('tooltip-hoverer');
            $(tooltip).text($(this).attr('data-tooltip'));
            var x = e.clientX - (($(tooltip).outerWidth()) / 2); // "($(tooltip).outerWidth() / 2)" is required to show the tooltip's down arrow next to the mouse's left position
            var y = this.getBoundingClientRect().top + this.offsetParent.scrollTop;
            $(tooltip).css({
                top: y - 32,
                left: x,
                display: 'block'
            });
        },
        mouseleave: function () {
            document.body.removeChild(tooltip);
        }
    });                       
});

Note that unlike Josh's solution, I don't create all the tool tips at load, but only one each time a span is hovered, then I remove it when the cursor leaves the span.

JSFiddle can be found here

like image 33
actaram Avatar answered Nov 14 '22 22:11

actaram