box-shadow and CSS transforms – A future without pngs

category-css3

Drop shadows and inset shadows are all the hit nowadays. Now with CSS3's box-shadow they're springing up everywhere, but there's still a lot of untapped power that is, for now, not possible on all modern browsers without using images. I'm going to show you how we can start using pure CSS to achieve results traditionally done with images, and make them dynamic!

NOTE: The examples in this article were tested with positive results in Firefox 7, Chrome 14, Safari 5.1, and Internet Explorer 9 (with the exception of the last example for FF7 and IE9).

View Demo

The Basics

First of all, let's wrap our collective head around exactly what each value in the box-shadow property does:

.shadow {
  box-shadow: 1px 1px 1px 1px #000;
}

/* let's abstract those values */
.shadow {
  box-shadow: [x-offset] [y-offset] [blur radius]* [spread]* [color];
}

1. [x-offset] shifts the box-shadow right for +ve values, and left for -ve values.

2. [y-offset] shifts the box-shadow down for +ve values, and up for -ve values.

3. [blur radius] blurs the shadow x number of pixels from the edge of the spread; its centre lying on the edge of the spread.

4. [spread] spreads the shadow x number of pixels out from the container in all directions, and without blurring. In a way, the spread adopts the specified colour, and is also what is moved when shifting the shadow horizontally and verically.

5. [color] is the shadow colour. I will be using HSLa colours for the examples.

*optional - default is 0

These can be better explained with an image:

I used blue for the blurs in the image to differentiate from the spread. Notice how the blur radius sits on the edge of the spread and spills over on each side (bottom right example - dotted line shows actual edge of spread).


Now for the fun stuff!

I will not be using vendor prefixes for all CSS3 properties for reasons of code brevity. Ensure you add the relevant vendor prefixes when using the following snippets.

TIP: box-shadow no longer requires vendor prefixes!


Shadow directly below container

<div class="box shadow-light-top"></div>

.box {
  background-color: #9C9369;
  width: 200px;
  height: 50px;
  margin: 50px auto;
}

.shadow-light-top {
  box-shadow: 0 8px 6px -6px hsla(0, 0%, 0%, 1);
}

The trick here is to use a -ve spread on the box-shadow. This shrinks the spread further under the container, so we need to shift the shadow down somewhat.

This shadow will work on all browsers supporting the box-shadow property.


Container with arched edges

This type of shadow can be seen on Elegant Themes, and even in the WordPress admin (Settings -> Writing)

<div class="box shadow-arch-edges"></div>

.box {
  background-color: #9C9369;
  width: 200px;
  height: 50px;
  margin: 50px auto;
}

.shadow-arch-edges {
  position: relative;
  box-shadow: 0 12px 10px -12px hsla(0, 0%, 0%, 1);
}

.shadow-arch-edges:before, .shadow-arch-edges:after {
  position: absolute;
  content: "";
  width: 15%;
  bottom: 10px;
  box-shadow: 0 0 12px 10px hsla(0, 0%, 0%, .5);
  z-index: -10;
}

.shadow-arch-edges:before {
  left: 24px;
  transform: skewY(-12.5deg);
}

.shadow-arch-edges:after{
  right: 24px;
  transform: skewY(12.5deg);
}

The idea here is to introduce the pseudo elements :before and :after, size them relative to the container (thereby making them dynamically resizable with the container), and skew them using transform: skewY([value]);. Setting a hardcoded left and right ensures that the pseudo elements lie ontop of where the container div's shadow lies.

Opera 11 requires a minimum height of 1px on the pseudo elements. The result is ugly no matter how I have attempted to fix the issue.


Container with arched centre

This shadow can be found on themes like Karma, and is popular as a shadow under sliders and carousels.

<div class="box shadow-arch-center"></div>

.box {
  background-color: #9C9369;
  width: 200px;
  height: 50px;
  margin: 50px auto;
}

.shadow-arch-center {
  position: relative;
}

.shadow-arch-center:before, .shadow-arch-center:after {
  position: absolute;
  content: "";
  bottom: 10px;
  z-index: -10;
}

.shadow-arch-center:before {
  left: 2%;
  right: 65%;
  
  /* as box-shadows get smaller, opacities increase */
  box-shadow: 80px 0px 20px 22px hsla(0, 0%, 0%, .01),
              70px 0px 20px 20px hsla(0, 0%, 0%, .02),
              60px 0px 20px 18px hsla(0, 0%, 0%, .04),
              50px 0px 20px 16px hsla(0, 0%, 0%, .08),
              40px 0px 20px 14px hsla(0, 0%, 0%, .16),
              30px 0px 20px 12px hsla(0, 0%, 0%, .16),
              20px 0px 20px 10px hsla(0, 0%, 0%, .25),
              10px 0px 20px 2px hsla(0, 0%, 0%, .5),
              0 0 20px 2px hsla(0, 0%, 0%, 1);

  transform: skewY(5deg);
}

.shadow-arch-center:after{
  left: 65%;
  right: 2%;
  
  /* as box-shadows get smaller, opacities increase */
  box-shadow: -80px 0px 20px 22px hsla(0, 0%, 0%, .01),
              -70px 0px 20px 20px hsla(0, 0%, 0%, .02),
              -60px 0px 20px 18px hsla(0, 0%, 0%, .04),
              -50px 0px 20px 16px hsla(0, 0%, 0%, .08),
              -40px 0px 20px 14px hsla(0, 0%, 0%, .16),
              -30px 0px 20px 12px hsla(0, 0%, 0%, .16),
              -20px 0px 20px 10px hsla(0, 0%, 0%, .25),
              -10px 0px 20px 2px hsla(0, 0%, 0%, .5),
              0 0 20px 2px hsla(0, 0%, 0%, 1);
  
  transform: skewY(-5deg);
}

And there I gone done it... a gradient on the pseudo elements created by increasing levels of opacity on smaller and smaller box-shadows. The pseudo elements are positioned with their left and right relative to the container.

This example required a considerable amount of fiddling with the first four values, and should be relatively easy to improve upon by tweaking what I've come up with.

Again, Opera 11 doesn't like pseudo elements without a minimum height of 1px.


Create a (levitating) sphere with an elliptical drop-shadow

<div class="box box-round square-150 shadow-sphere shadow-ellipse"></div>

.box {
  background-color: #9C9369;
  margin: 50px auto;
}

.box-round {
  border-radius: 50%;
}

.square-150 {
  width: 150px;
  height: 150px;
}

.shadow-sphere {
  box-shadow: inset 50px -50px 100px hsla(0, 0%, 0%, .6);
}

.shadow-ellipse { position: relative;}

.shadow-ellipse:after {
  position: absolute;
  content: "";
  height: 60%;
  left: 20%;
  right: 20%;
  bottom: -35%;
  box-shadow: 0 0 10px 10px hsla(0, 0%, 0%, .075),
              0 0 40px 20px hsla(0, 0%, 0%, .075),
              inset 0 0 50px 100px hsla(0, 0%, 0%, .15);
  border-radius: 50%;
  transform: rotateX(86deg);
  z-index: -10;
}

Creating the sphere is a matter of applying a large blur, and shifting the shadow to create the illusion of a light source. A drawback is that percentages can't be applied to the box-shadow values, so hardcoded values are required.

The elliptical drop-shadow can be created by creating a pseudo element as a circle, rotating it drastically, and applying box-shadows which will create a gradient outside of the ellipse. the box-shadow property doesn't extend both inside and outside of an element, so an inset box-shadow must be put in the mix. Setting its opacity to the sum of the two outside box-shadows should create an even effect.

Neither Internet Explorer 9, nor Firefox 7 show the drop-shadow correctly. IE9 does not support the transform: rotate([value]); property, while, strangely, FF7 doesn't support rotations on pseudo elements.

View Demo

Browser support for box-shadow

The box-shadow property is fully supported by major browsers:

  • Internet Explorer 9+
  • Safari/Chrome Webkit 5.0/4.0+
  • Firefox 4.0+
  • Opera 10.5+

For legacy browsers, it would be best to use your trusty old images should you feel it important to your content.


Let me know if any of these techniques crop up, or have cropped up anywhere!


1 Response

rss feed

    This comment thread is closed. Got something important to say? contact us!