Search code examples
htmlcsscss-transitionsbox-shadow

Why does a child div's box shadow sometimes jump to the parent div?


I'm trying to animate a box shadow transition with the aid of CSS pseudo-elements :before and :after. In principle, the code provided in the JSFiddle works fine, except that on mousing out of the subdiv in the lefthand column, its box shadow jumps to the leftparentdiv. This behavior occurs whenever the window is window is small enough that overflow-y: scroll kicks in. The problem seems to occur in all browsers that support box shadows.

I guess I'm missing something obvious here, but can't figure out what.

body {
    background-color: pink;
}
.subdiv {
    width: 25vw;
    border: 1px solid;
}
.subdiv:after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: -1;
    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.30);
    transition: all 0.6s ease-in-out;
    opacity: 0;
}
.subdiv:hover {
    transform: scale(1.1, 1.1);
}
.subdiv:hover:after {
    opacity: 1;
}
#leftparentdiv {
    position: absolute;
    left: 0;
    top: 0;
    border: 1px solid;
    width: 47vw;
    padding: 3%;
    overflow-y: scroll;

}
#rightparentdiv {
    position: absolute;
    left: 53vw;
    top: 0;
    border: 1px solid;
    width: 47vw;
    padding: 3%;
    overflow-y: scroll;
}
<div id="leftparentdiv">
    <div class="subdiv">
      Blabla;
    </div>
</div>
<div id="rightparentdiv">
    <div class="subdiv">
      Blabla;
    </div>  
</div>

JSFiddle


Solution

  • You have a transform on .subdiv:hover, but none on .subdiv. A box with a transform establishes a containing block. This is what allows the pseudo-element (and its shadow) to be painted around .subdiv when the mouse is over it. But when the mouse leaves the element, the pseudo-element (and its shadow) jumps to .subdiv's parent because .subdiv no longer establishes a containing block, so the pseudo-element sizes according to the parent element and not its originating .subdiv because the parent element does establish a containing block. This is also true when the layout is first rendered, before the cursor ever touches the .subdiv (you just don't see it because the pseudo-element is invisible).

    This behavior actually occurs only when .subdiv's parent has overflow: visible. It doesn't occur otherwise. The reason for that is because the pseudo-element's box shadow is actually overflowing the parent element whenever its originating .subdiv is not :hover. So a non-visible overflow clips the shadow away. This isn't immediately apparent because the parent element doesn't scroll — and the reason for that is because box shadows don't affect layout.

    Assuming the desired behavior is for .subdiv to always be the one casting the shadow, all you have to do is position .subdiv so it establishes a containing block at all times:

    body {
        background-color: pink;
    }
    .subdiv {
        width: 25vw;
        position: relative;
        border: 1px solid;
    }
    .subdiv:after {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: -1;
        box-shadow: 0 8px 25px rgba(0, 0, 0, 0.30);
        transition: all 0.6s ease-in-out;
        opacity: 0;
    }
    .subdiv:hover {
        transform: scale(1.1, 1.1);
    }
    .subdiv:hover:after {
        opacity: 1;
    }
    #leftparentdiv {
        position: absolute;
        left: 0;
        top: 0;
        border: 1px solid;
        width: 47vw;
        padding: 3%;
        overflow-y: scroll;
    
    }
    #rightparentdiv {
        position: absolute;
        left: 53vw;
        top: 0;
        border: 1px solid;
        width: 47vw;
        padding: 3%;
        overflow-y: scroll;
    }
    <div id="leftparentdiv">
        <div class="subdiv">
          Blabla;
        </div>
    </div>
    <div id="rightparentdiv">
        <div class="subdiv">
          Blabla;
        </div>  
    </div>