1 Thing A Week 1 Thing
A Week

Building a wavy background in CSS

Week 283.0 — 27th March '23

Last week I saw a tweet from @jamesm and just had to see if I could recreate it in CSS.

My initial attempt was pretty poor in the end, although I could imagine radial gradients being the answer, I just got the approach all wrong. My excuse is it was late and I was genuinely trying to put something together as quickly as I could. I didn't spend more than 15 minutes on it.

While I'd approached the radial gradient themselves well enough, it hit me the next morning that instead of lots of steps, I actually needed to break it all down into lots of individual gradients.

Not exactly the same as the original example, but the colours are tweak-able and for further sharing I decided to make the example orange to fit in more with my personal tastes.

As part of my write up, I reassessed how I'd approached the positioning which I had completely guessed and, for a reason I cannot remember, I went for negative values. And when I say I got it wrong, I had specified -1952% as one of the values.

Took a bit of guesswork before I could start to reign it in but settled on much more sensible numbers.

Explaining the CSS for the wavy background

:root

So, let's break the CSS down. At the top, there are some custom properties to set various color values. This just makes theming easier for the future.

:root {
  /* color scale */
  --color-8: #ff5500;
  --color-7: #e64d00;
  --color-6: #cc4400;
  --color-5: #b33b00;
  --color-4: #993300;
  --color-3: #802b00;
  --color-2: #662200;
  --color-1: #4c1900;
  --color-0: #FF5500;

  /* color scale for box shadows */
  --color-transparent-3: #802b0055;
  --color-transparent-2: #66220055;
  --color-transparent-1: #4c190055;
}

I'd also set some properties on the body to help with centring the element and if I set the font-family just to prove text fit in the box.

Onto the div itself (some CSS omitted for brevity), which I'll break down line by line (or property by property).

background-colour

This just sets the background color, should all else fail.

background-color: var(--color-6);

background-image

On to the background-image, with 4 separate radial gradients. As mentioned previously, breaking it down into 4 separate items, they can be positioned so that they can be offset against each other.

The gradients all have the same layout, just coloured differently for each step. They fade from a darker shade to a lighter one. The next step is two single pixel in an even lighter shade, which brings a nice and clear bit of separation between the layers. The last step is a fast fade to transparent which makes it the radial-gradient below visible.

background-image:
    radial-gradient(var(--color-3), var(--color-4) 22%, var(--color-5) calc(22%), var(--color-5) calc(22% + 1px), transparent calc(22% + 3px)),
    radial-gradient(var(--color-4), var(--color-5) 24%, var(--color-6) calc(24%), var(--color-6) calc(24% + 1px), transparent calc(24% + 3px)),
    radial-gradient(var(--color-5), var(--color-6) 26%, var(--color-7) calc(26%), var(--color-7) calc(26% + 1px), transparent calc(26% + 3px)),
    radial-gradient(var(--color-6), var(--color-7) 28%, var(--color-8) calc(28%), var(--color-8) calc(28% + 1px), transparent calc(28% + 3px));

One of the reasons I didn't push it to 100% is that radial gradients can stretch beyond the box. I just found that this was the sweet spot during experimentation.

background-position

Next up, we set the background position of each of the radial-gradient. We just need to push the background down enough that it is towards the bottom of the element. Because we've also played with the background-size, and the pattern should be offset the value differs for the X and Y values. Since the background-size has been exaggerated quite a lot, the step for each gradient is actually quite small to achieve the effect.

background-position: 48% 32.5%, 46.5% 33%, 45% 33.5%, 43.5% 34%;

background-size

To achieve the effect, and given that the gradients don't stretch to 100% of their available space, the background size needs to be exaggerated a fair bit. For this example, I found the sweet spot was 2000%.

background-size: 2000% 2000%;

Would I use this in production? I'd have to weigh it up against an image of the same size. I wonder if I could compress a SVG enough to make it as lightweight as the CSS would be. Minified, I managed to get the div's CSS down to ~900 bytes.

I'd also look to play with the figures to make the steps larger, but the background-size and background-position values smaller which then fit the mental model of how it should all fit together.

Potential issues

I originally only set a single value for background-size but Chrome required two (even if they're the same) for it to stretch correctly. Chrome however, does a better of job of rendering the step that shows a line between each gradient. Safari makes it look fine, the effect works but it's definitely rendered better in Chrome.

More from 1 Thing A Week
« Wealth Inequality Raising money for MIND at magLabs ChatGPT helped me build a functioning todo app! »

Advertisement

Notable

Quick links and commentary on interesting articles, videos and more throughout the week.

  1. 283.4 – Thursday
    1. How autonomous vehicles "see" the world around them axios.com

Jump to notable items for Week #282, Week #284 , view the notable archive or my favourites.

About 1 Thing A Week

A website curated by @cchana bringing you 1 Thing A Week, delivered every Monday morning.

You can find a list of all articles in the archive, or read the most popular.

Find out more about 1 Thing A Week or read the privacy policy.

Market

Visit the market and support 1 Thing A Week.

You can find 1 Thing A Week on Twitter, Facebook, Instagram & Reddit