4

I'm trying to add a box shadow on two elements, each with variable width. My desired result looks like this:

screenshot

I've been trying to get to this result with a pseudo element covering the overlapping box shadows, but because they need to have transparency, I can't seem to find a solution in which there are neither small overlaps at the edges of the boxes nor the pseudo element adjusts to the correct width. The top box does also not necessarily need a top border to solve my problem.

Fiddle

HTML:

<div>
    <p></p>
</div>
<div>
    <p></p>
</div>

SCSS:

div {
    display: inline-block;
    margin: 75px;
    width: 200px;
    height: 50px;
    position: relative;
    p {
        position: absolute;
        top: 100%;
        left: 0;
        height: 300px;
        width: 250px;
    }
    &, p {
        background: #ededed;
    }
}
div:last-child p {
    width: 150px
}

div {
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.2);
    p {
        box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.2);
    }    
}

Edit:

Normally I wouldn't consider JS for layout but since in my particular case the boxes won't be visible until a user interaction occurs, I've used a script to solve my problem. The script figures out if the top element is bigger than the bottom one when the dom is ready and adds a "big" or "small" class to it respectively. By knowing that, we know which element the pseudo-element's width should inherit. As long as the elements don't get resized in a way that would change which element is bigger, this works fine.

There is also a much cleaner solution without the need for JS and one pseudo element less in case one only needs box-sizing blur and no spread.

Fiddles:

Blur and spread combined (JS),

Only blur, no spread (No JS)

The end result is not quite perfect as you can see in this screenshot where all the white background is replaced with black:

Screenshot

When you look at the left box's top left, you can see that the border shadow has a slight curve. Anyway, it's close enough to me.

If someone finds a solution with a similar result as in the first fiddle using only css, I would really appreciate it.

Matthias
  • 737
  • 8
  • 14
  • If your shadow is this small, I recommend simply using `border`, it will look practically identical, have a simpler implementation, and have improved performance (notable on mobile devices). If you choose to go with drop shadows I believe placing the top element `z-index` behind the larger element should make everything work the way you want – Alexei Darmin Jul 25 '15 at 02:13
  • Borders wouldn't really work for me because their transparency is relative to the element they are added to and not its background. Editing the z-index just lets you decide which element's border should be overlapping the other one [Fiddle with different background-colors and negative z-index for child element](https://jsfiddle.net/Matze/5ymdL3uo/3/) – Matthias Jul 25 '15 at 09:53

2 Answers2

2

You have an easy solution for this, but it is an experimental feature and it has limited support.

Using a filter: drop shadow on the base element, the drop shadow applies to the composite result of this element, and all the descendants

div {
    display: inline-block;
    margin: 75px;
    width: 150px;
    height: 50px;
    position: relative; 
    -webkit-filter: drop-shadow(0px 0px 5px rgba(255, 0,0,0.7));
    filter: drop-shadow(0px 0px 2px red);
}

div p {
    position: absolute;
    top: 100%;
    left: 0;
    height: 300px;
    width: 250px; 
    margin: 0px;
}

div, div p {
    background: #ededed; 
}

#second p {
  width: 100px; 
}
<div>
    <p></p>
</div>
<div id="second">
    <p></p>
</div>

An alternate approach, that will run in any browser, using pseudo elements for the shadows:

div {
    display: inline-block;
    margin: 75px;
    width: 150px;
    height: 50px;
    position: relative; 
}

div p {
    position: absolute;
    top: 100%;
    left: 0;
    height: 300px;
    width: 250px; 
    margin: 0px;
}

div, div p {
    background: #ededed; 
}

#second p {
  width: 100px; 
}

div:after, p:after {
    content: "";
    position: absolute;
    left: 0px;
    top: 0px;
    right: 0px;
    bottom: 0px;
    box-shadow: 0px 0px 2px 6px rgba(0,255,0,0.7);
    z-index: -10;
}
<div>
    <p></p>
</div>
<div id="second">
    <p></p>
</div>

An alternate approach is to clip the shadows. That is poorly suported, and needs lots of manual adjustements, but the end result is probably the best looking. Demo working only in webkit

div {
    display: inline-block;
    margin: 75px;
    width: 300px;
    height: 50px;
    position: absolute; 
}

div p {
    position: absolute;
    top: 100%;
    left: 0;
    height: 100px;
    width: 200px; 
    margin: 0px;
}

div, div p {
    background: #ededed; 
}


div:after, p:after {
    content: "";
    position: absolute;
    left: 0px;
    top: 0px;
    right: 0px;
    bottom: 0px;
    box-shadow: 0px 0px 15px 15px rgba(255,0,0,0.2);
    z-index: -10;
}

p:after {
    -webkit-clip-path: polygon(0% 30px, 230px 30px, 260px 60px, 100% 100%, 0% 100%);
}

div:after {
    -webkit-clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 260px 100%, 230px 80px, 0% 80px);
    
}
<div>
    <p></p>
</div>
vals
  • 61,425
  • 11
  • 89
  • 138
  • Thanks for your answer, the drop shadow tip was really useful. If you play around with the pseudo-element approach though and use a bit more transparency, you see the little overlaps at the box edges I mentioned in my question: [Screenshot](https://i.imgur.com/6NduORT.png). Do you have any idea about how to find a cross browser solution that does not have these? – Matthias Jul 25 '15 at 09:33
  • Here is a nice article about cross browser drop shadows for anyone using this approach: http://demosthenes.info/blog/600/Creating-a-True-Cross-Browser-Drop-Shadow-Effect-With-CSS3-amp-SVG – Matthias Jul 25 '15 at 14:56
0

If you really need a plain color background instead of a background image, this shall work: I used a div to create the empty area.

<div class="shad">
    <div class="cover1"></div>
    <p></p>
</div>

<div class="shad">
    <div class="cover2"></div>
    <p></p>    
</div>

The paragraphs are set to same size as div.shad.

div.shad {
    display: inline-block;
    margin: 75px;
    width: 250px;
    height: 350px;
    position: relative;
    background: #ededed;
    p {
        position: absolute;
        top: 0%;
        left: 0;
        width: 250px;
        height: 350px;
    }
    .cover1 {
    position: relative;
    float: right;
    margin-top: -2px;
    margin-right: -2px;
    width: 50px;
    height: 50px;
    background-color: white;
    border-left: 2px solid rgba(0, 0, 0, 0.2);
    border-bottom: 2px solid rgba(0, 0, 0, 0.2);    
    }
    .cover2 {
    position: relative;
    float: right;
    margin-top: 50px;
    margin-right: -2px;
    width: 50px;
    height: 300px;
    background-color: white;
    border-top: 2px solid rgba(0, 0, 0, 0.2);
    border-left: 2px solid rgba(0, 0, 0, 0.2);
    }
}

div.shad {
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.2);     
}
Fionna
  • 382
  • 4
  • 10
  • This approach doesn't really solve my problem because it doesn't work with backgrounds other than white and the border of the masking div is not actually showing a shaded version of the background behind the .shaded element, but rather the masking element. I also need the elements' widths to be variable so that they can adjust to the size of their contents. [Fiddle with different colors](https://jsfiddle.net/Matze/60o9m18w/1/) – Matthias Jul 25 '15 at 09:18
  • Delete the background-color from .cover1 and .cover2. Add .cover1, .cover2 at where you define the background color. body, .cover1, .cover2 { background: #7098BD; } That will solve the coloring problem. For the widths, just set the widths to % instead of px. – Fionna Jul 25 '15 at 09:31
  • That would work with static colors but not with any other background. What I mean by variable widths is that the masking element would have to adapt to the .shad element's width, which is not determined by a value but by the size of its contents, text for example. That way, we don't know the width of the .shad element when writing the css. – Matthias Jul 25 '15 at 09:42