Linear gradients are easy to create in CSS and are extremely useful. As we’ll go through in this article, we can make them visually much smoother by creating them with non-linear gradients. Well, non-linear in the easing sense, anyway!
Here’s an example that shows how harsh a standard linear-gradient()
can be compared to how smooth we can make it by easing it:
- Il buono (the good): Smooth gradients in CSS that blends into their context.
- Il cattivo (the bad): No text protection (bad accessibility).
- Il brutto (the ugly): Standard linear gradients with sharp edges.
In this article, we’ll focus on how we can turn Il brutto into Il buono.
background: linear-gradient()
The Frustrating Sharp Edges of Lately, I’ve been fiddling with gradients at work. I got frustrated with plain linear gradients because they looked like Il cattivo above.
/* Sharp edges :( */
.image__text {
background-image: linear-gradient(
hsla(0, 0%, 0%, 0.6),
transparent;
);
}
I started looking into creating consistently more visually appealing gradients. More accurately, I quickly eyeballed some prettier-looking gradients as one-offs and then started tinkering when I got home.
Inspiration: Math and Physics
Since a gradient is a transition of color, I got inspired by how we approach transitions elsewhere.
I’ve always been fascinated by the The Euler (or Cornu) Spiral, which has a curvature that increases linearly with the curve length, i.e., as we walk along the line from (0, 0) the radius decreases linearly with how far we walk (since the curvature is the reciprocal of the radius).
The end result is a curve that transitions as smoothly as possible from a straight line to a curve. (Side note: straight lines in Euclidian space are curves with an infinite radius!)
This type of curve is called a transition curve and is used in the real word. Next time when you exit a well-built highway, then notice how gradually you turn the steering wheel. We can thank Euler for keeping sudden changes in centripetal acceleration to an absolute minimum, i.e., his math is the reason the car doesn’t flip over just as we exit the highway even at the highway speed limit.
The image below is an example of how gradual changes the changes are in the curvature of highways.
Inspiration: Typography
Type designers throughout history have been obsessed with smooth curves. We’ve done so because we don’t want the letters and numbers to look like a combination of different shapes but in itself form a coherent shape.
It’s why I’ve made the transition from a straight line to the circle in the “9” below as smooth as possible. It makes the number 9 read as a single shape and not a line plus a circle.
As type designers we have tools to help us achieve this. FontForge, an open source font editor, even has a Spiro/Euler drawing mode. One of the most popular type design extensions is Speed Punk, which visualizes the curvature.
Inspiration: Design
Apple uses this approach to line-curve transitions heavily in both digital and hardware design departments. (See Apple’s Icons Have That Shape for a Very Good Reason). When Apple launched iOS7, the icon masks were updated to have a much smoother transition from straight line to rounded corners.
(Side note: The iOS7 shape above is taken directly from Apple HIG, which unfortunately has some minor imperfections especially where the horizontal lines start curving. It’s also sometimes known as a “Squircle”.)
Inspiration: Web Design
In web design, we’ve been limited in some cases by what we’re able to do. For example, border-radius
doesn’t offer any way to make a squircle like the iOS7 icon. Similarly with linear-gradient
, there is no natural easings available.
However, we do have easings and bezier curves available in animations! They have enabled us to make animations look more natural, smooth, and subtle.
Gradient Implications
Most times, in web design, we want the gradient to blend in as much as possible. When we have a text protection gradient like Il buono, we don’t want the user to pay attention to the gradient itself. It should be rather invisible, thus, allowing the reader to focus on the image and text.
Scrim
In Material Design style guidelines for images, the designers at Google talk about text protection gradients. They call them a scrim. They recommend:
[the] gradient should be long… with the center point about 3/10 towards the darker side of the gradient. This gives the gradient a natural falloff and avoids a sharp edge.
We can’t create exactly that with linear gradients, but we can (and will) create a “low poly” approximation with more color stops.
Using only 5 color stops (like in the illustration above) would create some serious banding. Adding more stops makes the gradient a lot smoother. This is exactly what I’ve done in the demo you saw in the first image in this article. Il buono has a 13 color-stop gradient, which makes it blend nicer into the image.
See the Pen The Good, The Bad & The Ugly – correct text by Andreas Larsen (@larsenwork) on CodePen.
Compared to the Material Design scrim, I’ve tweaked it to be a bit more linear in the beginning to achieve higher text contrast and gave it a smoother fade out.
If we compare the Material Design scrim to a plain linear gradient, then it will have to be ~60% longer to achieve the same half way contrast whereas my attempt only has to be ~30% longer. The idea is to avoid darkening more of the image than necessary but still blend in smoothly with it.
I’ve chosen not to include the Material Design scrim as it’s almost identical to the prettier easeOutSine
. We can compare how linear-gradient
, my scrim-gradient
and ease-out-sine-gradient
looks here:
Blending Both Ends
In the scrim example, we only need to blend one end as the other ends with the image. Sometimes, we need to blend in at both ends, and that’s where the easing functions, such as easeInOutSine
comes in handy.
Using an easeInOut
function, we make sure that both the transition from colorA to gradient and gradient to colorB is as smooth as possible. The same principle is illustrated in this Pen:
How to Draw the Gradients
On YouTube, there’s a gradient behind the controls when you hover over a video. It’s created using a base64 PNG. This technique also works really well, but you can’t really automate it or easily tweak it.
Most other places use a linear-gradient(hsla(0, 0%, 0%, 0.8), transparent)
where the start alpha most times is between 0.6-0.8. This solves the issue of making the text readable, but the resulting gradient is very prominent. An example of this is BBC where I tried using the scrim-gradient instead:
PostCSS to the Rescue
I created a plugin that creates these gradients for me. Here’s the syntax:
scrim-gradient(
black,
transparent
);
becomes:
linear-gradient(
hsl(0, 0%, 0%) 0%,
hsla(0, 0%, 0%, 0.738) 19%,
hsla(0, 0%, 0%, 0.541) 34%,
hsla(0, 0%, 0%, 0.382) 47%,
hsla(0, 0%, 0%, 0.278) 56.5%,
hsla(0, 0%, 0%, 0.194) 65%,
hsla(0, 0%, 0%, 0.126) 73%,
hsla(0, 0%, 0%, 0.075) 80.2%,
hsla(0, 0%, 0%, 0.042) 86.1%,
hsla(0, 0%, 0%, 0.021) 91%,
hsla(0, 0%, 0%, 0.008) 95.2%,
hsla(0, 0%, 0%, 0.002) 98.2%,
hsla(0, 0%, 0%, 0) 100%
);
The underlying principle is the one we’ve gone through: combining easing functions and multiple color stops to create approximations that look smoother than plain linear-gradients.
Actually, the scrim-gradient above is generated using a custom set of coordinates but if we look at the easing gradients such as ease-in-out-sine-gradient
the steps are:
- Run through the .css (or .pcss) and find it.
- Generate evenly distributed coordinates along the ease-in-out-sine curve.
- Use these coordinates to create color stops. The x-coordinate determines the color mixing ratio, and the y-coordinate determines the color stop position.
- Replace the easing-gradient with the generated linear-gradient.
The plugin currently has two optional settings:
precision
— correlates to number of color stops generatedalphaDecimals
— sets the number of decimals used in thehsla()
alpha values
The Plugin: postcss-easing-gradients
Overall, I’m fairly happy with the output. Things I’m considering to add:
- Sass version? I’m not using it anymore. Perhaps, someone else would like to write one?
- The coordinates used to mix the colors for the color stops are distributed evenly over the easing curves. Ideally, the distance between the color stops would be relatively shorter where the delta change in curvature is biggest.
- Maybe you folks have some ideas?
You can find it on GitHub, NPM and as a template on CodePen. Go play around with it! Your contributions and suggestions are very welcome.
CSS
Preferably, I’d like to be able to write something like ease-in-out-gradient(#bada55, transparent)
and have the browser understand it without having to do any custom CSS processing.
References/Further Reading
- Chris Coyer: Design Considerations: Text on Images
- Patrick Brosset: Do you really know CSS linear-gradients?
- MDN: Linear Gradient Specification
- Marcus Tisäter: So you want to make a PostCSS plugin
- GitHub: PostCSS Plugin Boilerplate
- Jonathan Neal: CodePen with any PostCSS Plugin
This is a very confusing post. First of all, it’s hard to extract the actual code from the animation code that switches between examples. Separate pens would have been more useful to learn from.
Also, where is this postcss plugin, in case we want to use it? I’m not familiar with postcss, as we use scss.
Sorry it was confusing to you.
There are links to GitHub, NPM and a codepen template of the plugin at the end of the article.
As for the code examples the idea was that users just use the “view compiled” function in codepen to see the css output.
Correction: you have to use the inspector to see the processed code but I’ve updated it so it’s comments inside the pcss :)
There is no need to turn it into a sass function if it’s available as a post-css plugin.
Sass is compiled first then it gets parsed by post-css so it’s 100% Sass compatible already without having to ecplicitly make a sass function for it.
Cheers, also why it wasn’t really something I’d want to look into.
But some use sass exclusively so for them it might be easier to have a mixin or something like it.
Nice and interesting article :)
Just a small detail on Italian: Il brutto is the ugly, while il cattivo means the bad. Maybe Leone was cheating on you? ;)
In fact, in your example introductory GIF, the bad was ugly and the ugly was bad :D
Ah dammit, thought I double checked. Will notify @chris so we can update the article.
Thank you! :)
Great article! I always wondered what I was doing wrong when I’d end up with those hideous linear gradient artifacts. Makes you wonder if one day there’ll be a
bezier-gradient()
function. Or maybe even aborder-bezier
property, to allow for things like the Apple icons.Thank you, probably not a bezier-gradient but we’ve started discussing already https://github.com/w3c/csswg-drafts/issues/1332
The border-radius one is more tricky but something more flexible than just border radius would be nice.
Great post, Andreas! I went ahead and created a basic SCSS mixin that replicates the scrim gradient you created. You can find it in this CodePen. Suggestions welcome!
Awesome,
Suggestions:
Would be nice if it could handle mixing of two colors so it’s not just transparent.
Also nice if it supported ease-in-sine, ease-out-sine and ease-in-out-sine too as those are some of the pretty and flexible ones.
If you need the map for those you can easily console.log them from the postcss plugin.
It’s not as flexible as the easing curves described here, but native CSS now has a way to create curved gradients of a sort:
You specify a stop with a position but no color (known as a color hint or midpoint hint), like this:
linear-gradient(black, 30%, transparent)
The browser puts the mid-point color (i.e.,
hsla(0,0%,0%, 0.5)
) at the color hint position (i.e., at 30%), and then uses a gamma (logarithmic) curve to smooth the gradient on either side.To create an ease-in curve (that is: the color change starts slow, then speeds up), you’d want a color hint greater than 50%. To create an ease-out curve (color change starts quickly, then slows down), you’d want a hint less than 50%.
To create an ease-in and ease-out S-curve, you’d need two pairs of stops with hints. I found this worked nicely in your demo, after extending the gradient to 125% of the height of the linear gradient:
linear-gradient(black, 30%, hsla(0, 0%, 0%, 0.5) 50%, 70%, transparent);
Color hints are natively supported in the latest versions of all browsers, but you’d need fallbacks for older browsers. Unfortunately, it doesn’t look like AutoPrefixer currently does anything to recognize color hints in the gradient stop list and create fallbacks for them.
Nice, had completely forgot about those — will have to play around with it a bit to see how much is possible.
As a side note I wouldn’t use the s-shaped one for the text-protection but could maybe work well in the other example http://codepen.io/larsenwork/pen/dvBrMv
Cool!
How about browser support?
Amazing article! It provides a great framework for thinking about colors and animations too as I look into more interactive UI designs going forward.
Cheers, glad you liked it. Can highly recommend reading the material guidelines about animations — they’re really well written and with many examples.
I love this idea, and it exactly addresses to the continual problems I have trying to create smoothly fading out gradients.
I might take a stab at making a Sass mixin, only because I still use Sass in my every day coding life (am I the last web developer doing that?).
But what would make more sense, I think, than
ease-in-out-gradient( direction, #COLOR1, #COLOR2)
would be to add an easing parameter to the existinglinear-gradient()
andradial-gradient()
CSS functions. Something liketransition
already uses:linear-gradient( direction, #COLOR1, #COLOR2, ease-in-out)
. Just require it to be the last comma-separated value. If it makes sense to the browser as an easing value, it uses it, otherwise it treats it like a color. So backwards compatible too!Just a thought.
Thank you!
And you’re not the only one at all — I’m just not the right one to create it since I’m not using it any more and have very limited advanced sass knowledge:)
As for the syntax then it’s already being discussed here: https://github.com/w3c/csswg-drafts/issues/1332
Nope, I love sass too!
I created a simple mixing here: https://codepen.io/lhermann/pen/qmpMGQ
Not very refined yet, but looking great already.
Thanks for the post! We recently updated our video thumbnails throughout the site, and ran into the same problems, with a similar solution.
http://www.bravotv.com/videos
Excellent article, really good looking results!
I also wrote a dumbed down SCSS implementation. It’s a scrim-linear-gradient function, which can be used in the same way as the native linear-gradient function. Example: https://codepen.io/jmkII/pen/pParwj
Here is a simple LESS version of the function to generate a single color to transparent scrim
(for LESS 1.5+)
Hi there. I registered just to say, that this is the best post that I read on CSS Tricks. It incredible! And PostCSS plugin is AWESOME!!! Thank you!
Well thank you very much. Glad you liked it:)