Easing Linear Gradients

Avatar of Andreas Larsen
Andreas Larsen on (Updated on )

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:

Screencap from “The Good, the Bad and the Ugly” with gradients overlaid.
  • 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.

The Frustrating Sharp Edges of background: linear-gradient()

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!)

Euler Spiral by AdiJapan, CC BY-SA 3.0

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.

Intersection outside of Sagamihara, Japan by @digitalanthill

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.

Spiro mode in FontForge on the left and Speed Punk visualisation inside Glyphs on the right.

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.

Visualisation of the curvature of the iOS6 and iOS7 app icons.

(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.

Screenshot from easings.net

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.

A scrim according to Material Design guidelines

We can’t create exactly that with linear gradients, but we can (and will) create a “low poly” approximation with more color stops.

A scrim with 5 color stops to show the principle

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.

Comparison between the Material Design scrim and mine drawn using 13 color stops.

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.

The two scrims compared to a plain linear gradient

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.

Linear-gradient vs. ease-in-out-sine approximation.

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:

See the 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.

Screenshot from YouTube (artist is Wintergatan).

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:

Screenshots from BBC with linear-gradient (left) and scrim-gradient (right).

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:

  1. Run through the .css (or .pcss) and find it.
  2. Generate evenly distributed coordinates along the ease-in-out-sine curve.
  3. Use these coordinates to create color stops. The x-coordinate determines the color mixing ratio, and the y-coordinate determines the color stop position.
  4. Replace the easing-gradient with the generated linear-gradient.
Ease-in-out-sine coordinate values generated in step 2.

The plugin currently has two optional settings:

  • precision — correlates to number of color stops generated
  • alphaDecimals — sets the number of decimals used in the hsla() 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