Heygrady (there's a new blog)

I don't get It.

Recreating Photoshop Drop Shadows in CSS3 and Compass


A challenge all web developers face is converting Photoshop documents into real, functioning web pages. Often this is a simple matter of measuring things and picking colors, but in the case of drop shadows recreating them in CSS3 can be challenging. For some reason I have yet to find a tool that will simply translate the settings from the Photoshop Layer Style Drop Shadow dialog into proper CSS3 — so I decided to create one in Compass. I created a Sass @mixin that relies on Compass to easily create CSS3 box-shadows from the values found in Photoshop.

UPDATE: a Github repo and Ruby Gem have been created.

Photoshop Layer Style Drop Shadow Dialog

Photoshop Layer Style Drop Shadow Dialog
Photoshop Drop Shadow Dialog (Internet)

Above you can see the Drop Shadow dialog in Photoshop. There is an article online that does a great job of breaking down what each of the properties does. Once it’s understood how Photoshop uses the values it’s easy to translate those values into CSS3 box-shadow values.

Blend Mode
Blends the shadow color with the background. CSS does not offer a similar property; this makes it difficult to match the colors exactly. CSS approximates a blend mode of “Normal”, but Photoshop uses “Multiply” by default. Using black (#000) will produce the same shadow in “Normal” and “Multiply”.
Sets the opacity of the shadow. This is similar to using rgba().
Note: rgba() is currently not supported in CSS3PIE for box-shadow.
Controls the directions of the shadow. The Angle can be replicated using the <offset-x> and <offset-y> values of box-shadow and can be calculated with a little trigonometry.
Controls the shadow offset in the direction of the Angle. This combines with the Angle for calculating the <offset-x> and <offset-y>.
Determines the portion of the shadow (percentage of the Size) that is a solid color; the rest of the shadow is blurred. Spread is analogous to the <spread-radius> value of box-shadow.
Sets the radius of the shadow in pixels. In CSS3 the Size is the <spread-radius> plus the <blur-radius>.
Contour & Noise:
CSS3 box-shadow has no equivalent values.

CSS3 Box Shadow

Now that we have a basic understanding of what Photoshop is doing, let’s look at the values that box-shadow accepts and see how they compare.

<offset-x> and <offset-y>
Moves the shadow on the x and y axis. These values are required and are always the first two length properties. These are calculated using the Angle and Distance in Photoshop.
The radius of the blur in addition to the spread. A blur of 10px with a spread of 10px will result in a 20px shadow where the first 10 pixels (the spread) is solid and the last 10 pixels gradually fade out (the blur). This is always the third length value and is optional.
Just like Photoshop, the spread is the portion of the shadow that is a solid color. Unlike Photoshop, spread is expressed in pixels instead of as a percentage of the total Size. A spread of 10px with a 0px blur will look similar to a 10px solid border. Spread is always the fourth length value and is optional.
Sets the color of the shadow. If a blur is set the shadow will automatically be slightly transparent. The spread area of the shadow will appear in the color specified. To have the entire shadow, including spread, appear transparent use rgba().

Generating the CSS3 Values from the Photoshop Values

As mentioned before, although Photoshop and CSS3 represent the values differently, they are actually analogous to each other.

Calculating the Offsets

Photoshop uses Angle and Distance, which can be used to calculate the <offset-x> and <offset-y>. Imagine a right triangle where the <offset-x> and the <offset-y> formed the right angle. The Distance would be the hypotenuse and the Angle is the inner angle. There are online triangle calculators for this basic trigonometry. Again, the <offset-y> and <offset-x> are the opposite and adjacent sides, the Distance is the hypotenuse.

x-offset y-offset distance angle
Triangle formed by <offset-x>, <offset-y>,
Distance and Angle. (requires modern browser)

Using this basic understanding of triangles it becomes easy to calculate the offsets using the Photoshop Drop Shadow values. As the online triangle calculator linked above points out, Sin(Θ) = Opposite / Hypotenuse. More importantly, Opposite = Sin(Θ) * Hypotenuse. The same is true of the adjacent side: Adjacent = Cos(Θ) * Hypotenuse. And if you’re remembering your trig properly — or looking at the cheat sheet — you know that only two values are needed to calculate any other two. Thankfully, basic trig functions were added to Compass in version 0.11.

For those who are still totally lost:

  • Angle == Θ
  • Distance == Hypotenuse;
  • <offset-y> == Opposite;
  • <offset-x> == Adjacent;

Calculating the Blur and Spread

The <blur-radius> and <spread-radius> are not quite as straightforward but are equally easy to calculate. In Photoshop, the Size attribute represents the total length of the shadow and the Spread represents the percentage of the shadow that is a solid color — the rest is blurred. In CSS the spread is added to the blur for the total shadow size. With this in mind, <blur-radius> is equal to the Photoshop Size minus the <spread-radius> and the <spread-radius> equals the Photoshop Size multiplied by the Photoshop Spread percentage. Again, the code example above bears this out.

The @mixin

Below is a Sass @mixin that takes values from Photoshop, converts them, and creates a CSS3 box-shadow.

photoshop-drop-shadow.scssGist page
@import "compass/css3/box-shadow";
@import "compass/css3/text-shadow";
// Photoshop Drop Shadow
@mixin photoshop-drop-shadow ($angle: 0, $distance: 0, $spread: 0, $size: 0, $color: #000, $inner: false) {
$angle: (180 - $angle) * pi() / 180; // convert to radians
$h-shadow: round(cos($angle) * $distance);
$v-shadow: round(sin($angle) * $distance);
$css-spread: $size * $spread/100;
$blur: ($size - $css-spread);
$inset: if($inner != false, 'inset', '');
@include box-shadow($h-shadow $v-shadow $blur $css-spread $color unquote($inset));
// Photoshop Inner Shadow
@mixin photoshop-inner-shadow ($angle: 0, $distance: 0, $spread: 0, $size: 0, $color: #000) {
@include photoshop-drop-shadow ($angle, $distance, $spread, $size, $color, true);
// Photoshop Text Shadow
@mixin photoshop-text-shadow ($angle: 0, $distance: 0, $spread: 0, $size: 0, $color: #000) {
// NOTE: $spread has no effect for text shadows
$angle: (180 - $angle) * pi() / 180;
$h-shadow: round(cos($angle) * $distance);
$v-shadow: round(sin($angle) * $distance);
$css-spread: $size * $spread/100;
$blur: ($size - $css-spread);
@include text-shadow($h-shadow $v-shadow $blur $color);

The code above relies on the Compass box-shadow() @mixin to generate the correct CSS rules with all of the correct vendor prefixes.

It’s worth noting that two additional @mixins are shown in the code above. @mixin photoshop-inner-shadow is for Photoshops Inner Shadow Layer Style, which is analogous to the inset value for box-shadow. @mixin photoshop-text-shadow is for text-shadow which is implemented in Photoshop as a Drop Shadow applied to a text layer. It’s also worth noting that CSS does not support a Spread on text-shadows.

Seeing It In Action

An image of red box with a drop
shadow created in Photoshop.
A red box with a box-shadow
(view live example).

Using the @mixin is quite simple. Assume that we want to recreate the above Photoshop image in CSS. This image was created with the default Blend Mode, Color and Opacity. The Angle is 120°, the Distance is 10px, the Spread is 50% and the Size is 10px. As the example below shows, all that is needed is to plug in the values as they appear in Photoshop into the @mixin.

example.scssGist page
.box {
@include photoshop-drop-shadow(120, 10px, 50, 10px, rgba(0, 0, 0, 0.75));

The @mixin will automatically convert the values and create the corresponding CSS rules as shown in the example below. It is important to note that the Angle and Spread values should always be unitless while the Distance and Size values require px as the unit.

example.cssGist page
.box {
-moz-box-shadow: 5px 9px 5px 5px rgba(0, 0, 0, 0.75);
-webkit-box-shadow: 5px 9px 5px 5px rgba(0, 0, 0, 0.75);
-o-box-shadow: 5px 9px 5px 5px rgba(0, 0, 0, 0.75);
box-shadow: 5px 9px 5px 5px rgba(0, 0, 0, 0.75);