A very common design pattern is you’ll find on websites is a centered content area with a drop shadow, which creates the effect of lifting the content away from the page background. I’m talking about something like this:
As developers, there are numerous options available to us to create that drop shadow, and while I’ll touch on some typical methods, I’ll focus on what I believe to be a less common technique and in which circumstances it would be good to use.
Why Not Just Use an Image?
The most common and time-tested method to create the drop shadow is to simply use an image that repeats vertically, on the Y axis. Usually, such an image would be defined as the background-image
on the <body>
element. In many cases, that works just fine and that’s all you’ll need to do. Sometimes, though, you’ll need to have a different image in the background over which the content area and it’s drop shadow lives, as was the case on a project I recently worked on. In that project’s case, the page background was a sort of blue spotlight under the content area which had to be its own image. In other cases, there may be some sort graphically busy background, or even a background with a photograph or advertising, similar to something you might see on sites like IMDB.
You can still use an image for the drop shadow in those cases. To do so, you would either have to define multiple background images on the body element or wrap your content in an extra non-semantic element to give your CSS different elements to which it can apply background images to (particularly if you want things to look the same in older browsers).
Personally, with more CSS3 capabilities available to us with each new browser update, I am using fewer and fewer images and relying more heavily on CSS3 to code up layouts which are true to the original designs. I find this approach more efficient, both in time saved during production (anything that minimizes my time in Photoshop is a good thing in my opinion), as well as in site performance once it goes live (fewer images = fewer assets to download = lighter page weights = less bandwidth used = faster load times…oh you get the idea!).
The CSS3 Approach
CSS3 offers a wonderful property for this sort of thing: box-shadow. With it, we can easily create the drop shadow on your content area with a bit of simple CSS, like so:
article {
-webkit-box-shadow: 0 0 64px rgba(0, 0, 0, 0.6);
-moz-box-shadow: 0 0 64px rgba(0, 0, 0, 0.6);
box-shadow: 0 0 64px rgba(0, 0, 0, 0.6);
}
You can see the results on my example page. Looks great, right? There’s a lot of text on that page, though, so a user would have to scroll a lot if they were reading it. Did you try scrolling? If not, go back and do so. Be sure to grab that scrollbar and fling it up and down quickly. You’ll probably find the scrolling to be a bit sluggish and jerky. If you’re on a Mac and are using a Webkit based browser like Safari or Chrome, you’ll see that the scrolling is extremely sluggish and jerky. Go ahead and compare that scrolling performance to the same page without box-shadow
. It’s a big difference, no?
Obviously, using box-shadow
on large elements can have a detrimental effect on performance and how users perceive the page. So what do we do if we don’t want to use an image for the drop shadow and want to maintain good scrolling performance?
Enter Canvas
My solution to that question involves using a simple canvas
element onto which we draw a rectangle with a shadow and position it behind the content. Pretty simple, really.
My original thought about this approach involved setting the canvas
element to the same height as the content element and drawing the rectangle with the shadow to that height. I quickly realized that method isn’t really very efficient and is definitely not scalable. If the content gets very tall (as is the case in the example page), the browser would have to work too hard drawing that drop shadow for the entire height of the page, while a user would only see a bit of it at a time. That’s when I remembered position: fixed
.
Why Make the Browser Draw More than it Needs To?
By using position: fixed
on the canvas
and setting its height to match the height of the browser window rather that of the content, we draw only as much shadow as we need to create our effect, and no more. This way, we give the illusion that the drop shadow spans the entire height of the content. Assuming that I’ve wrapped my content in an article
element, the CSS to make this happen looks something like this:
article {
margin: 0 auto;
padding: 0 64px 64px;
position: relative;
width: 640px;
z-index: 1;
}
canvas {
left: 50%;
margin-left: -448px;
position: fixed;
top: 0;
z-index: 0;
}
You may be wondering why the margin-left
on the canvas is -448px. It is simply half the width of the article
element (320px) plus the width of its left padding (64px) plus the width of the drop shadow on one side of the content (also 64px). This ensures that the canvas is centered behind the content.
Of course, a canvas
element by itself doesn’t do anything at all. You need to use a bit of Javascript to draw anything on it. I’ve scripted it like so:
var PageShadow = function(){
var page,
shadow,
page_height,
page_width,
context,
shadow_width = 64, // The width of the dropshadow on either side of the article
// Function to draw the drop shadow
draw = function() {
// Make sure we have a canvas to work with and its drawing context
if ( ! context || ! shadow ) {
return;
}
var canvas_height = window.innerHeight,
// The Math.min() ensures that if the content is shorter
// than the window, then the shadow is drawn appropriately.
shadow_height = Math.min(window.innerHeight, page.offsetHeight);
// Clear the canvas
context.clearRect(0, 0, page_width + (shadow_width * 2), shadow.offsetHeight);
// Set the height of the canvas to match the window
shadow.height = canvas_height;
// Set up the Drop Shadow
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = shadow_width;
context.shadowColor = "rgba(0,0,0,0.6)";
// Set up the background to match the content background
context.fillStyle = "rgb(248,248,248)";
// Draw the box to set the drop shadow
context.fillRect(shadow_width, 0, page_width, shadow_height);
};
return {
setup : function(){
// Find the elements we're working with.
page = document.getElementsByTagName('article');
shadow = document.getElementsByTagName('canvas');
// Drop out if we don't have those elements
if ( ! page.length || ! shadow.length ) {
return;
}
// Make sure we're working with single elements, not an array.
page = page[0];
shadow = shadow[0];
// Get the content dimensions
page_height = page.offsetHeight;
page_width = page.offsetWidth;
// Make sure our canvas is scriptable
if ( shadow.getContext ) {
// Set the canvas width
shadow.width = page_width + (shadow_width * 2);
// Set up our drawing context
context = shadow.getContext('2d');
// Draw the initial drop shadow
draw();
// Redraw the shadow on window resize
window.addEventListener('resize', draw, false);
}
}
};
}();
// Initialize the shadow drawing on Dom Ready.
document.addEventListener('DOMContentLoaded', PageShadow.setup, false);
The comments in the code should be pretty self-explanatory and guide you through what’s happening there. You can see the results on the canvas drop shadow example page. Compare it side by side with the CSS3 example and you should notice a big difference in the scrolling, particularly in Webkit browsers.
I’ve taken into account what happens when you resize the window and when the content is shorter than the page. Note that Chrome can be a little sluggish with redrawing the page when resizing the window. The wider and more diffuse your shadow, the more sluggish Chrome feels with it. I feel this is an OK tradeoff when using this technique though, as a user is typically much more likely to scroll a page than resize the window.
What about Browser Support?
This technique works well in the latest versions of just about every major browser. Safari, Chrome, Firefox 3.5+ and Opera all support it. What about Internet Explorer, you ask? Thankfully, IE9 handles this with grace and aplomb. IE8 and earlier, however, don’t do canvas
. While there are workarounds to add canvas support to older IEs, in this case, I feel that some conditional comments targeting IE8 and earlier can feed those browsers an image-based solution and you can be done with it.
In conclusion, I hope you find this technique to be useful, and perhaps one that you can add to your toolbox. Let me know if you try it out in a real world situation and how it works out for you.