Search code examples
csscss-shapescss-transformsbox-shadow

Incorrect stack with box-shadow and transform


I have created a shape which represents a page with a shadow that gets bigger towards the bottom.

body {
  background: #dddddd;
}
div {
  background: white;
  margin: 40px auto;
  height: 300px;
  width: 300px;
  position: relative;
  padding: 10px;
}
div:before,
div:after {
  height: 96%;
  z-index: -10;
  position: absolute;
  content: "";
  left: 8px;
  top: 2%;
  width: 30%;
  max-width: 300px;
  background: transparent;
  box-shadow: -10px 0px 10px rgba(0, 0, 0, 0.5);
  transform: rotate(1.5deg);
}
div:after {
  transform: rotate(-1.5deg);
  right: 8px;
  left: auto;
  box-shadow: 10px 0px 10px rgba(0, 0, 0, 0.5);
}
<div></div>

I need this to be rotated but when i try to add transform: rotate(10deg), the box-shadow illusion gets ruined and goes on top of the parent layer.

body {
  background: #dddddd;
}
div {
  background: white;
  margin: 40px auto;
  height: 300px;
  width: 300px;
  position: relative;
  padding: 10px;
  transform: rotate(10deg);
}
div:before,
div:after {
  height: 96%;
  z-index: -10;
  position: absolute;
  content: "";
  left: 8px;
  top: 2%;
  width: 30%;
  max-width: 300px;
  background: transparent;
  box-shadow: -10px 0px 10px rgba(0, 0, 0, 0.5);
  transform: rotate(1.5deg);
}
div:after {
  transform: rotate(-1.5deg);
  right: 8px;
  left: auto;
  box-shadow: 10px 0px 10px rgba(0, 0, 0, 0.5);
}
<div></div>

I have found this question: Which CSS properties create a stacking context? but there doesn't seem to be a proposed solution for my requirement.

Would there be any good solutions which would work in my case. I do not mind if they are SVG, filter, canvas or any thing else as long as it is supported reasonably well.


Solution

  • Note: This answer does not describe how to fix the stacking context problem that is seen in your approach. This just provides a couple of alternate approaches that could be used to achieve a similar effect. Advantage of these approaches is that they should work in IE10+ and does not require any extra elements.

    I would still recommend vals' answer if IE support is not mandatory.

    Method 1: Perspective Transform

    This is almost similar to the one that you had used except that it uses a single pseudo-element rotated with perspective to produce the shadows. Since only one pseudo-element is utilized, the other pseudo can be used to add a white foreground above the shadows.

    body {
      background: #dddddd;
    }
    div {
      position: relative;
      height: 300px;
      width: 300px;
      padding: 10px;
      margin: 40px auto;
      transform: rotate(10deg);
    }
    div:before,
    div:after {
      position: absolute;
      content: '';
      top: 0px;
    }
    div:before {
      height: 100%;
      width: 100%;
      left: 0px;
      background: white;
    }
    div:after {
      height: 98%;
      width: 97%;
      left: 1.5%;
      transform-origin: bottom;
      transform: perspective(125px) rotateX(1deg);
      box-shadow: 10px 0px 10px rgba(0, 0, 0, .5), -10px 0px 10px rgba(0, 0, 0, .5);
      z-index: -1;
    }
    <div></div>


    Method 2: Linear Gradients

    We can use linear-gradient background images and position them appropriately to produce an effect similar to the one produced by the box-shadows. But as you can see in the output, it doesn't quite match up to a shadow because the blurred areas are not the same.

    Here, we make use of the following:

    • One small angled linear gradient image (to top left) to produce the shadow on the left side of the box.
    • Another small angled linear gradient image (to top right) to produce the shadow on the right side of the box.
    • A large linear-gradient image for the white colored area (which is almost a solid color). A gradient is used here instead of a solid color because the size of a gradient image can be controlled.

    body {
      background: #dddddd;
    }
    div {
      margin: 40px auto;
      height: 300px;
      width: 300px;
      transform: rotate(10deg);
      backface-visibility: hidden;
      background: linear-gradient(to right, transparent 0.1%, white 0.1%), linear-gradient(to top left, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .3) 5%, transparent 50%), linear-gradient(to top right, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .3) 5%, transparent 50%);
      background-size: 280px 100%, 10px 97%, 10px 97%;
      background-position: 10px 0px, left top, right top;
      background-repeat: no-repeat;
      background-origin: border-box;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
    <div></div>

    The bottom of the gradient still doesn't get the blur that is seen in the box-shadow output. If needed, this can be achieved to some extent by adding even more gradients like in the below snippet.

    body {
      background: #dddddd;
    }
    div {
      margin: 40px auto;
      height: 300px;
      width: 300px;
      transform: rotate(10deg);
      backface-visibility: hidden;
      background: linear-gradient(to right, transparent 0.1%, white 0.1%), linear-gradient(to top left, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .3) 5%, transparent 50%), linear-gradient(to top right, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .3) 5%, transparent 50%), linear-gradient(to bottom left, rgba(0, 0, 0, 0), rgba(0, 0, 0, .3) 5%, transparent 60%), linear-gradient(to bottom right, rgba(0, 0, 0, 0), rgba(0, 0, 0, .3) 5%, transparent 70%);
      background-size: 280px 100%, 10px 97%, 10px 97%, 10px 2.5%, 10px 2.5%;
      background-position: 10px 0px, left top, right top, left 99.25%, right 99.25%;
      background-repeat: no-repeat;
      background-origin: border-box;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
    <div></div>