You know the long shadow design trend, right? Although, I’m not even sure this is a trend anymore, everything moves so fast… Anyway, long shadows used to be and / or still are a trendy design trick to give some emphasis to some text or an element.
There is no easy way to create a long shadow in CSS. At the end of the day, either you use some image-based format (an actual image, SVG…) or you rely on text-shadow
and box-shadow
. The latter options are better because they are nothing but CSS that can be handled very nicely by the browser.
The Problem is: that it is a pain to write. To achieve a clean long shadow effect, you have to build a chain upwards of 50 shadows or so, slightly shifting values one step at a time, possibly the color as well… Completely impractical. This is typically the kind of thing you want to automate with Sass or whatever tool floats your boat.
Indeed, there have been many attempts at making a long shadow mixin. Among the solutions I have seen, I have noticed problems, including:
- no way to define the direction;
- no way to define the number of shadows;
- only works for
text-shadow
; - overly complicated.
Well fasten your seat belt folks because I have found what I dare to call a clean solution. I have built a ~15 lines long property-agnostic long-shadow function that accepts a direction (as well as a few other arguments).
The API
Because we want our tool to work with both box-shadow
and text-shadow
properties, we will not actually build a mixin like the title of the article says, but actually a function. Then, you’ll be able to use it like this:
.foo {
box-shadow: long-shadow($args...);
}
.bar {
text-shadow: long-shadow($args...);
}
Indeed, box shadows and text shadows are almost identical except that box shadows have a blur parameter that we will not use. Note that Internet Explorer 10+ supports blur for text shadows as well… True story.
Our function needs a couple of things to work:
- a direction, in a similar fashion to
linear-gradient
so either a keyword or an angle (indeg
,rad
,grad
orturn
); - a length;
- a color;
- whether or note the shadow should fade (the default is
false
); this is either a boolean (wheretrue
means fading to transparent) or a color to fade to; - the number of shadows to compute (the default is
100
).
/// Function to generate long shadows (because flat is so has-been).
/// Property-agnostic: works for both `box-shadow` and `text-shadow`.
/// `cos` and `sin` might need to be polyfilled if Compass or any
/// equivalent such as SassyMath is not in use.
///
/// @author Hugo Giraudel
///
/// @link https://unindented.org/articles/trigonometry-in-sass/ Pure Sass `cos` and `sin`
///
/// @param {Direction} $direction
/// Shadow's direction (angle or keyword)
/// @param {Length} $length
/// Shadow's length
/// @param {Color} $color
/// Shadow's color
/// @param {Bool | Color} $fade [false]
/// Whether or not shadow should fade:
/// - `false` means no fading, shadow is `$color`
/// - `true` means fading from `$color` to transparent
/// - a color means fading from `$color` to `$fade`
/// @param {Number} $shadow-count [100]
/// Number of computed shadows
///
/// @return {List} - List of shadows
///
/// @require {function} Compass/helpers/math/cos
/// http://compass-style.org/reference/compass/helpers/math/#cos
/// @require {function} Compass/helpers/math/sin
/// http://compass-style.org/reference/compass/helpers/math/#sin
///
/// @example scss - Usage
/// .foo {
/// text-shadow: long-shadow(42deg, 1em, #16a085);
/// }
/// .bar {
/// box-shadow: long-shadow(to top left, 150px, hotpink, tomato);
/// }
@function long-shadow($direction, $length, $color, $fade: false, $shadow-count: 100) {}
Building the function
Now let’s get started with the function. The first thing we have to do is convert $direction
to an angle if it is a keyword (e.g. to right
being 90deg
). Doing so is extremely straightforward; we only need a conversion map.
$conversion-map: (
to top: 180deg,
to top right: 135deg,
to right top: 135deg,
to right: 90deg,
to bottom right: 45deg,
to right bottom: 45deg,
to bottom: 0deg,
to bottom left: 315deg,
to left bottom: 315deg,
to left: 270deg,
to left top: 225deg,
to top left: 225deg
);
@if map-has-key($conversion-map, $direction) {
$direction: map-get($conversion-map, $direction);
}
At this point, there is only one thing left to do (yes, already!): iterating from 1 through $shadow-count
, each time computing a new shadow.
// ...
$shadows: ();
@for $i from 1 through $shadow-count {
// ...
$shadow: ...;
$shadows: append($shadows, $shadow, 'comma');
}
@return $shadows;
Now we will need to compute $shadow
. At first I thought it would be a pain, but it turns out to be basic trigonometry. We only need to compute the sin of the angle for the x offset and the cos of the angle for the y offset. Then, we multiply both by the length of the long shadow divided by the number of total shadows, multiplied by the current index of $i
.
$x: sin(0deg + $direction) * ($i * $length / $shadow-count);
$y: cos(0deg + $direction) * ($i * $length / $shadow-count);
You may wonder why we append the $direction
(an angle) to 0deg
. Actually, this is a clever way to cast the second value into the first’s unit. In this case, if $direction
is expressed in gradients, radians or turns, it is automatically converted in its equivalent in degrees.
Regarding the color, it is a bit trickier. Depending on the value of $fade
, we have three options:
- if
$fade
isfalse
, then the color is$color
; - if
$fade
istrue
, then we fade from$color
to transparent; - if
$fade
is a color, then we fade from$color
to$fade
.
So unless $fade
is false
, the color slightly varies at each loop run, so we will need to compute it every time.
// If `$fade` is `false`
$current-color: $color;
// If `$fade` is a color
@if type-of($fade) == 'color' {
$current-color: mix($fade, $color, ($i / $shadow-count * 100);
// If `$fade` is `true`
} @else if $fade {
$current-color: rgba($color, 1 - $i / $shadow-count);
}
… or as a one liner:
$current-color: if(not $fade, $color, if(type-of($fade) == 'color', mix($fade, $color, ($i / $shadow-count * 100)), rgba($color, 1 - $i / $shadow-count)));
So at this point our shadow (to be appended to the shadows list) is:
$shadow: $x $y 0 $current-color;
The whole function
@function long-shadow($direction, $length, $color, $fade: false, $shadow-count: 100) {
$shadows: ();
$conversion-map: (
to top: 180deg,
to top right: 135deg,
to right top: 135deg,
to right: 90deg,
to bottom right: 45deg,
to right bottom: 45deg,
to bottom: 0deg,
to bottom left: 315deg,
to left bottom: 315deg,
to left: 270deg,
to left top: 225deg,
to top left: 225deg
);
@if map-has-key($conversion-map, $direction) {
$direction: map-get($conversion-map, $direction);
}
@for $i from 1 through $shadow-count {
$current-step: ($i * $length / $shadow-count);
$current-color: if(not $fade, $color, if(type-of($fade) == 'color', mix($fade, $color, ($i / $shadow-count * 100)), rgba($color, 1 - $i / $shadow-count)));
$shadows: append($shadows, (sin(0deg + $direction) * $current-step) (cos(0deg + $direction) * $current-step) 0 $current-color, 'comma');
}
@return $shadows;
}
Examples
.foo {
text-shadow: long-shadow(
// Shadow should have an angle of 42 degrees
$direction: 42deg,
// Shadow should be contain within a 100x100 box
$length: 100px,
// Shadow should start this color
$color: #16a085,
// To finish this color
$fade: #1abc9c
);
}
![long shadow example 1](https://uploads.sitepoint.com/wp-content/uploads/2015/01/1421924364kpHbb6e.png)
.bar {
box-shadow: long-shadow(
// Shadow should go to bottom right (45deg)
$direction: to left,
// With a length of 15em
$length: 15em,
// From this color
$color: #2980b9,
// To this color
$fade: #e67e22
);
}
![long shadow example 2](https://uploads.sitepoint.com/wp-content/uploads/2015/01/1421924374uo6625L.png)
.baz {
box-shadow: long-shadow(
// Shadow should have an angle of 25deg
$direction: -125deg,
// Spread on 120px
$length: 120px,
// From this color
$color: #8e44ad,
// To transparent
$fade: true,
// With only 10 shadows
$shadow-count: 10
)
}
![long shadow example 3](https://uploads.sitepoint.com/wp-content/uploads/2015/01/1421924369ECeMqZF.png)
Final thoughts
That’s it my friends! I hope you like both the power and the simplicity of this approach. Feel free to suggest any improvement and be sure to have a look at the code on CodePen!
See the Pen The Ultimate Long-shadow Sass Mixin by SitePoint (@SitePoint) on CodePen.
Frequently Asked Questions on Ultimate Long Shadow Sass Mixin
What is a Sass Mixin and how does it work?
A Sass Mixin is a block of code that lets us group CSS declarations we may want to reuse throughout our site. You can think of it as a CSS code snippet, which you can then include in your other Sass files. This is a great feature of Sass as it promotes code reuse and modularity. Mixins can also take parameters, which allows you to produce a wide variety of styles with very few mixins.
How can I create a long shadow effect using Sass Mixin?
Creating a long shadow effect using Sass Mixin involves defining a mixin that generates a series of box shadows, each one slightly offset from the previous one. The mixin takes parameters for the color and length of the shadow, and uses a loop to generate the shadow. Here’s a basic example:@mixin long-shadow($color, $length) {
$shadow: 0 0 0 $color;
@for $i from 1 through $length {
$shadow: $shadow, $i*1px $i*1px 0 $color;
}
box-shadow: $shadow;
}
How can I use multiple box shadows in Sass?
Multiple box shadows can be declared in Sass by separating them with commas. Each box shadow is defined by its horizontal offset, vertical offset, blur radius, spread radius, and color. Here’s an example:box-shadow: 0 1px 2px rgba(0,0,0,0.15),
0 2px 5px rgba(0,0,0,0.1),
0 3px 7px rgba(0,0,0,0.05);
What are some other useful Sass Mixins?
There are many useful Sass Mixins that can help streamline your CSS development. Some examples include mixins for vendor prefixes, responsive design breakpoints, and CSS3 animations and transitions. You can also create your own mixins to encapsulate complex or repetitive styles.
How can I create reusable CSS with Mixins?
Mixins in Sass allow you to create reusable chunks of CSS that can be included in other Sass files. This is done by defining a mixin with the @mixin
directive, and then including it with the @include
directive. For example, you might create a mixin for a button style that you use frequently:@mixin button-style {
background-color: blue;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}
.button {
@include button-style;
}
In this example, the .button
class will have all the styles defined in the button-style
mixin.
Non-binary trans accessibility & diversity advocate, frontend developer, author. Real life cat. She/her.