The following is a guest post by Parker Bennett. Parker is a bit of a regular around here, known for tackling common problems with unique solutions. This time he’s back at it creating complex shapes that have image backgrounds, shadows, and curves, yet are flexible.
I love to collaborate on design because it often pushes me to try new things. Recently, I was given a design comp something like this: a rounded compound shape serving as a fixed header, with a textured inset that had an inner shadow all around (a “well”). Well, I thought, how hard could this be?
One answer was super easy, of course: I could have opted to slice up an image and use transparent .png files for the curvy bottom – shadow and all – with an “end-cap” that would allow the right-half to resize responsively (the first column was designed to be fixed-width).
But I wanted more flexibility down the road, so I decided to tackle it using CSS. Here’s the approach I wound up taking, Nothing whiz-bangy, just good old CSS:
- Edit on CodePen (just the gist)
- Edit on CodePen (full finished layout)
CodePen is Sassy!
CodePen turned out to be indispensable for all my experimentation, as well as for client review. One big advantage was being able to write in SCSS and quickly see the result: I could try different widths, colors, shadows, border radii, etc., just by changing variables.
I used variables for certain heights and widths, as well as box-shadow
and border-radius
sizes, then used those to calculate additional sizes – using ratios where it made sense to tie elements of the design together.
In a Fix
For most projects I use a centered .page-wrap
that’s a percentage of the browser window. But when you pin your header to the top using position: fixed
, it’s no longer contained by the page-wrap. One solution is to start the page-wrap after the fixed header, and include a header-wrap that duplicates the width, margins, and padding of the page-wrap (details in this CodePen).
To maintain the illusion of the scrolling content being in a “cut-out” section below, I used overflow: hidden
to crop the header’s drop shadow at the sides. This meant a second wrapper div, in order to also maintain a little padding between the content and the window frame at small widths (yes, I’m just that anal).
z-index: Leveling Up
Naturally, I wanted to keep the markup lean, and not depend on source order for the layout. I used pseudo elements where they seemed to make sense, and relied a lot on absolute positioning to put things in their place – using z-index
to change how they overlapped. Because absolute positioning removes the element from the layout flow, I would need to compensate at times, for example, adding padding
to other elements to fill that space.
As I worked out the positioning, I found I could paint myself into a z-index corner thanks to the peculiarities of stacking order: z-index
requires positioning to work, un-positioned elements render first, and it’s easy to lose track of what elements are constrained by “stacking context” – for example, pseudo-elements can’t rise above the z-index
of their parent (see demo on CodePen).
I also changed the z-index
of shadows, making them a separate pseudo element. This let me tuck shadows behind objects where they needed to be obscured or, as on the sides of the .main
scrolling content, raise them up to cover anything that reached the edge so as not break the illusion of depth.
/* elements that reach the edges of .main need to fall
under edge shadow to maintain "cut-out" illusion */
.well-sides {
/* child elements absolute */
position: relative;
overflow: hidden; }
.well-sides:before, .well-sides:after {
content: "";
display: block;
position: absolute;
/* higher than any z-index in .main, lower than header */
z-index: 99;
/* shadow size */
top: -20px;
height: 120%;
/* shadow size */
width: 20px;
background: transparent; }
.well-sides:before {
left: 0;
/* negative spread */
-webkit-box-shadow: inset 20px -20px 20px -20px rgba(0,0,0,0.35);
-moz-box-shadow: inset 20px -20px 20px -20px rgba(0,0,0,0.35);
box-shadow: inset 20px -20px 20px -20px rgba(0,0,0,0.35); }
.well-sides:after {
right: 0;
/* negative spread */
-webkit-box-shadow: inset -20px 20px 20px -20px rgba(0,0,0,0.35);
-moz-box-shadow: inset -20px 20px 20px -20px rgba(0,0,0,0.35);
box-shadow: inset -20px 20px 20px -20px rgba(0,0,0,0.35); }
Cutting Corners
Unfortunately, there’s no practical way to create an object with a concave curve in CSS. Where the two rounded rectangles joined, I used a “patch” to cover the inset
box-shadow
“seam” and extend the textured background into the area of the inner corner. Then I overlapped it with a rounded white corner, creating the negative space as a positive. Finally, I added a normal box-shadow
to the rounded corner (cropped by overflow: hidden
) to blend it in with the inset
box-shadow
.
For the other inner curves I positioned a transparent square with one rounded corner and a thick white border on either side to complete the illusion. I used the border-radius
and box-shadow
sizes as variables to determine the size and placement of the corners.
How It All Stacks Up
Here’s how I put the pieces together:
Slideshow here. If reading through syndication, see the real blog post to see the slideshow.
Mind …. blown. Simply awesome solution that now makes me want to refactor a bunch of code!!
Very clever, but it’s not a technique that anybody is going to use on a regular basis. It feels nasty and hacky.
This is definitely a problem that needs solving and css doesn’t currently provide a solution.
Gotta agree with you – definitely feels nasty and hacky.
It’s complex, but I wouldn’t consider anything here a ‘hack’ (in the relevant sense of the term, namely using a tool to achieve something it wasn’t designed for). But yeah, this does serve to highlight a few places where the current state of CSS is sorely lacking.
WebKit has the awesome drop-shadow filter, which gets around the overlapping shadow issue (it will draw the shadow taking pseudo-elements into consideration, see this CodePen).
Awsome solution.
Useful Trick :)
I don’t know if ‘hacky’ is quite fair – I just can’t feel comfortable with all the extraneous non-semantic markup.
Wow what a great post. I’ve been experimenting with css to create complex rounded edges a lot like this. Thanks so much for the post. This is just what I was looking for!
Great post! Digging the ‘How It All Stacks Up’ to see the progression. Thanks!
Another awesome post.
Thank you so much Chris!!!
This front end work is hands down brutally creative, it’s mind blowing. Hat tip to you Mr. Parker Bennett.
But I have to agree with Jon Hobbs and G Givan above, it’s just not easy to overlook the “…extraneous non-semantic markup.” as G Givan says.
The other side of the coin is the actual design you were given: this is a clear example of a Graphic Designer doing Web Design. Totally “unrealistic” and undermined design. That designer is one of the luckiest people on the planet because he/she had you, an incredibly knowledgeable developer, implement his/her design.
This whole thing you did here reminds me of the days when I was in college and I was learning design (for print) and we used CorelDRAW (yeah, no need for Illustrator at all ;]) and we were all using white boxes to hide things around and whatnot. When you looked at the designs in wireframe mode, you could see all these rectangles all over the design (especially on the sides covering elements that were bleeding). Only to find out later on the amazing power of Powerclips :).
CSS has still so much to learn…
Technically speaking, rad. Practically speaking, not rad.
If I were to see a design like that with the beveled edge look and rounded corners without seeing the code, I would have immediately assumed it was a sliced up PSD rocking some transparent PNGs.
Reminds me of something I created at University in 2005. I would never design a site like that ever again :)
So much awkward space. I don’t want CSS to much this layout easier to create… just a step backwards in my opinion.