Tuesday, April 30, 2024
HomeCSSMethods to Create Wavy Shapes & Patterns in CSS | CSS-Tips

Methods to Create Wavy Shapes & Patterns in CSS | CSS-Tips


The wave might be one of the crucial troublesome shapes to make in CSS. We at all times attempt to approximate it with properties like border-radius and many magic numbers till we get one thing that feels kinda shut. And that’s earlier than we even get into wavy patterns, that are harder.

“SVG it!” you would possibly say, and you might be most likely proper that it’s a greater option to go. However we’ll see that CSS could make good waves and the code for it doesn’t should be all loopy. And guess what? I’ve a web based generator to make it much more trivial!

For those who play with the generator, you’ll be able to see that the CSS it spits out is just two gradients and a CSS masks property — simply these two issues and we are able to make any form of wave form or sample. To not point out that we are able to simply management the scale and the curvature of the waves whereas we’re at it.

A number of the values might seem like “magic numbers” however there’s really logic behind them and we’ll dissect the code and uncover all of the secrets and techniques behind creating waves.

This text is a follow-up to a earlier one the place I constructed every kind of various zig-zag, scoped, scalloped, and sure, wavy border borders. I extremely advocate checking that article because it makes use of the identical approach we’ll cowl right here, however in better element.

The maths behind waves

Strictly talking, there isn’t one magic components behind wavy shapes. Any form with curves that go up and down may be known as a wave, so we aren’t going to limit ourselves to complicated math. As a substitute, we’ll reproduce a wave utilizing the fundamentals of geometry.

Let’s begin with a easy instance utilizing two circle shapes:

Two gray circles.

Now we have two circles with the identical radius subsequent to one another. Do you see that purple line? It covers the highest half of the primary circle and the underside half of the second. Now think about you’re taking that line and repeat it.

A squiggly red line in the shape of waves.

We already see the wave. Now let’s fill the underside half (or the highest one) to get the next:

Red wave pattern.

Tada! Now we have a wavy form, and one which we are able to management utilizing one variable for the circle radii. This is likely one of the best waves we are able to make and it’s the one I confirmed off in this earlier article

Let’s add a little bit of complexity by taking the primary illustration and transferring the circles a bit of:

Two gray circles with two bisecting dashed lines indicating spacing.

We nonetheless have two circles with the identical radii however they’re not horizontally aligned. On this case, the purple line not covers half the realm of every circle, however a smaller space as an alternative. This space is proscribed by the dashed purple line. That line crosses the purpose the place each circles meet.

Now take that line and repeat it and also you get one other wave, a smoother one.

A red squiggly line.
A red wave pattern.

I feel you get the concept. By controlling the place and dimension of the circles, we are able to create any wave we would like. We will even create variables for them, which I’ll name P and S, respectively.

You might have most likely observed that, within the on-line generator, we management the wave utilizing two inputs. They map to the above variables. S is the “Measurement of the wave” and P is the “curvature of the wave”.

I’m defining P as P = m*S the place m is the variable you alter when updating the curvature of the wave. This enables us to at all times have the identical curvature, even when we replace S.

m may be any worth between 0 and 2. 0 will give us the primary explicit case the place each circles are aligned horizontally. 2 is a form of most worth. We will go greater, however after a couple of exams I discovered that something above 2 produces dangerous, flat shapes.

Let’s not overlook the radius of our circle! That may also be outlined utilizing S and P like this:

R = sqrt(P² + S²)/2

When P is the same as 0, we may have R = S/2.

Now we have all the pieces to begin changing all of this into gradients in CSS!

Creating gradients

Our waves use circles, and when speaking about circles we discuss radial gradients. And since two circles outline our wave, we’ll logically be utilizing two radial gradients.

We’ll begin with the actual case the place P is the same as 0. Right here is the illustration of the primary gradient:

This gradient creates the primary curvature whereas filling in the whole backside space —the “water” of the wave so to talk.

.wave {
  --size: 50px;

  masks: radial-gradient(var(--size) at 50% 0%, #0000 99%, purple 101%) 
    50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

The --size variable defines the radius and the scale of the radial gradient. If we examine it with the S variable, then it’s equal to S/2.

Now let’s add the second gradient:

The second gradient is nothing however a circle to finish our wave:

radial-gradient(var(--size) at 50% var(--size), blue 99%, #0000 101%) 
  calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%

For those who verify the earlier article you will note that I’m merely repeating what I already did there.

I adopted each articles however the gradient configurations are usually not the identical.

That’s as a result of we are able to attain the identical outcome utilizing totally different gradient configurations. You’ll discover a slight distinction within the alignment should you examine each configurations, however the trick is similar. This may be complicated if you’re unfamiliar with gradients, however don’t fear. With some follow, you get used to them and you will see that by your self that totally different syntax can result in the identical outcome.

Right here is the complete code for our first wave:

.wave {
  --size: 50px;

  masks:
    radial-gradient(var(--size) at 50% var(--size),#000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--size) at 50% 0px, #0000 99%, #000 101%) 
      50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

Now let’s take this code and alter it to the place we introduce a variable that makes this absolutely reusable for creating any wave we would like. As we noticed within the earlier part, the principle trick is to maneuver the circles so they’re no extra aligned so let’s replace the place of every one. We’ll transfer the primary one up and the second down.

Our code will seem like this:

.wave {
  --size: 50px;
  --p: 25px;

  masks:
    radial-gradient(var(--size) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--size) at 50% calc(-1*var(--p)), #0000 99%, #000 101%) 
      50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
}

I’ve launched a brand new --p variable that’s used it to outline the middle place of every circle. The primary gradient is utilizing 50% calc(-1*var(--p)), so its middle strikes up whereas the second is utilizing calc(var(--size) + var(--p)) to maneuver it down.

A demo is price a thousand phrases:

The circles are neither aligned nor contact each other. We spaced them far aside with out altering their radii, so we misplaced our wave. However we are able to make things better up by utilizing the identical math we used earlier to calculate the brand new radius. Keep in mind that R = sqrt(P² + S²)/2. In our case, --size is the same as S/2; the identical for --p which can be equal to P/2 since we’re transferring each circles. So, the gap between their middle factors is double the worth of --p for this:

R = sqrt(var(--size) * var(--size) + var(--p) * var(--p))

That offers us a results of 55.9px.

Our wave is again! Let’s plug that equation into our CSS:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p) * var(--p) + var(--size)*var(--size));

  masks:
    radial-gradient(var(--R) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0 / calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(-1*var(--p)), #0000 99%, #000 101%) 
      50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

That is legitimate CSS code. sqrt() is a part of the specification, however on the time I’m penning this, there isn’t any browser assist for it. Which means we want a sprinkle of JavaScript or Sass to calculate that worth till we get broader sqrt() assist.

That is fairly darn cool: all it takes is 2 gradients to get a cool wave that you may apply to any ingredient utilizing the masks property. No extra trial and error — all you want is to replace two variables and also you’re good to go!

Reversing the wave

What if we would like the waves going the opposite course, the place we’re filling within the “sky” as an alternative of the “water”. Imagine it or not, all we have now to do is to replace two values:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p) * var(--p) + var(--size) * var(--size));

  masks:
    radial-gradient(var(--R) at 50% calc(100% - (var(--size) + var(--p))), #000 99%, #0000 101%)
      calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(100% + var(--p)), #0000 99%, #000 101%) 
      50% calc(100% - var(--size)) / calc(4 * var(--size)) 100% repeat-x;
}

All I did there may be add an offset equal to 100%, highlighted above. Right here’s the outcome:

We will take into account a extra pleasant syntax utilizing key phrase values to make it even simpler:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size) * var(--size));

  masks:
    radial-gradient(var(--R) at left 50% backside calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at left 50% backside calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% backside var(--size) / calc(4 * var(--size)) 100% repeat-x;
}

We’re utilizing the left and backside key phrases to specify the edges and the offset. By default, the browser defaults to left and prime — that’s why we use 100% to maneuver the ingredient to the underside. In actuality, we’re transferring it from the prime by 100%, so it’s actually the identical as saying backside. A lot simpler to learn than math!

With this up to date syntax, all we have now to do is to swap backside for prime — or vice versa — to alter the course of the wave.

And if you wish to get each prime and backside waves, we mix all of the gradients in a single declaration:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  masks:
    /* Gradient 1 */
    radial-gradient(var(--R) at left 50% backside calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      left calc(50% - 2*var(--size)) backside 0 / calc(4 * var(--size)) 51% repeat-x,
    /* Gradient 2 */
    radial-gradient(var(--R) at left 50% backside calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% backside var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x,
    /* Gradient 3 */
    radial-gradient(var(--R) at left 50% prime calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      left calc(50% - 2 * var(--size)) prime 0 / calc(4 * var(--size)) 51% repeat-x,
    /* Gradient 4 */
    radial-gradient(var(--R) at left 50% prime calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% prime var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x;
}

For those who verify the code, you will note that along with combining all of the gradients, I’ve additionally lowered their top from 100% to 51% in order that they each cowl half of the ingredient. Sure, 51%. We want that little additional % for a small overlap that keep away from gaps.

What in regards to the left and proper sides?

It’s your homework! Take what we did with the highest and backside sides and attempt to replace the values to get the precise and left values. Don’t fear, it’s simple and the one factor you have to do is to swap values.

You probably have bother, you’ll be able to at all times use the net generator to verify the code and visualize the outcome.

Wavy strains

Earlier, we made our first wave utilizing a purple line then crammed the underside portion of the ingredient. How about that wavy line? That’s a wave too! Even higher is that if we are able to management its thickness with a variable so we are able to reuse it. Let’s do it!

We’re not going to begin from scratch however fairly take the earlier code and replace it. The very first thing to do is to replace the colour stops of the gradients. Each gradients begin from a clear colour to an opaque one, or vice versa. To simulate a line or border, we have to begin from clear, go to opaque, then again to clear once more:

#0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%

I feel you already guessed that the --b variable is what we’re utilizing to manage the road thickness. Let’s apply this to our gradients:

Yeah, the result’s removed from a wavy line. However trying intently, we are able to see that one gradient is appropriately creating the underside curvature. So, all we actually have to do is rectify the second gradient. As a substitute of maintaining a full circle, let’s make partial one like the opposite gradient.

Nonetheless far, however we have now each curvatures we want! For those who verify the code, you will note that we have now two similar gradients. The one distinction is their positioning:

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
  masks:
    radial-gradient(var(--R) at left 50% backside calc(-1*var(--p)), var(--_g)) 
      calc(50% - 2*var(--size)) 0/calc(4*var(--size)) 100%,
    radial-gradient(var(--R) at left 50% prime    calc(-1*var(--p)), var(--_g)) 
      50% var(--size)/calc(4*var(--size)) 100%;
}

Now we have to alter the scale and place for the ultimate form. We not want the gradient to be full-height, so we are able to change 100% with this:

/* Measurement plus thickness */
calc(var(--size) + var(--b))

There isn’t any mathematical logic behind this worth. It solely must be sufficiently big for the curvature. We’ll see its impact on the sample in only a bit. Within the meantime, let’s additionally replace the place to vertically middle the gradients:

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;  
  masks:
    radial-gradient(var(--R) at left 50% backside calc(-1*var(--p)), var(--_g)) 
      calc(50% - 2*var(--size)) 50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat,
    radial-gradient(var(--R) at left 50% prime calc(-1 * var(--p)), var(--_g)) 50%
      50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat;
}

Nonetheless not fairly there:

One gradient wants to maneuver a bit down and the opposite a bit up. Each want to maneuver by half of their top.

We’re virtually there! We want a small repair for the radius to have an ideal overlap. Each strains have to offset by half the border (--b) thickness:

We obtained it! An ideal wavy line that we are able to simply alter by controlling a couple of variables:

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: calc(sqrt(var(--p) * var(--p) + var(--size) * var(--size)) + var(--b) / 2);

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
  masks:
    radial-gradient(var(--R) at left 50% backside calc(-1 * var(--p)), var(--_g)) 
     calc(50% - 2*var(--size)) calc(50% - var(--size)/2 - var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x,
    radial-gradient(var(--R) at left 50% prime calc(-1*var(--p)),var(--_g)) 
     50%  calc(50% + var(--size)/2 + var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x;
}

I do know that the logic takes a bit to know. That’s positive and as I stated, making a wavy form in CSS just isn’t simple, to not point out the tough math behind it. That’s why the on-line generator is a lifesaver — you’ll be able to simply get the ultimate code even should you don’t absolutely perceive the logic behind it.

Wavy patterns

We will make a sample from the wavy line we simply created!

Oh no, the code of the sample might be much more obscure!

Under no circumstances! We have already got the code. All we have to do is to take away repeat-x from what we have already got, and tada. 🎉

A pleasant wavy sample. Keep in mind the equation I stated we’d revisit?

/* Measurement plus thickness */
calc(var(--size) + var(--b))

Properly, that is what controls the gap between the strains within the sample. We will make a variable out of it, however there’s no want for extra complexity. I’m not even utilizing a variable for that within the generator. Perhaps I’ll change that later.

Right here is similar sample getting in a special course:

I’m offering you with the code in that demo, however I’d so that you can dissect it and perceive what adjustments I made to make that occur.

Simplifying the code

In all of the earlier demos, we at all times outline the --size and --p independently. However do you recall how I discussed earlier that the net generator evaluates P as equal to m*S, the place m controls the curvature of the wave? By defining a hard and fast multiplier, we are able to work with one explicit wave and the code can grow to be simpler. That is what we’ll want normally: a particular wavy form and a variable to manage its dimension.

Let’s replace our code and introduce the m variable:

.wave {
  --size: 50px;
  --R: calc(var(--size) * sqrt(var(--m) * var(--m) + 1));

  masks:
    radial-gradient(var(--R) at 50% calc(var(--size) * (1 + var(--m))), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(-1 * var(--size) * var(--m)), #0000 99%, #000 101%) 
      50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
  }

As you’ll be able to see, we not want the --p variable. I changed it with var(--m)*var(--size), and optimized a number of the math accordingly. Now, If we wish to work with a selected wavy form, we are able to omit the --m variable and change it with a hard and fast worth. Let’s strive .8 for instance.

--size: 50px;
--R: calc(var(--size) * 1.28);

masks:
  radial-gradient(var(--R) at 50% calc(1.8 * var(--size)), #000 99%, #0000 101%) 
    calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
  radial-gradient(var(--R) at 50% calc(-.8 * var(--size)), #0000 99%, #000 101%) 
    50% var(--size) / calc(4 * var(--size)) 100% repeat-x;

See how the code is simpler now? Just one variable to manage your wave, plus you no extra have to depend on sqrt() which has no browser assist!

You’ll be able to apply the identical logic to all of the demos we noticed even for the wavy strains and the sample. I began with an in depth mathmatical clarification and gave the generic code, however you might end up needing simpler code in an actual use case. That is what I’m doing on a regular basis. I not often use the generic code, however I at all times take into account a simplified model particularly that, in many of the instances, I’m utilizing some recognized values that don’t should be saved as variables. (Spoiler alert: I might be sharing a couple of examples on the finish!)

Limitations to this method

Mathematically, the code we made ought to give us good wavy shapes and patterns, however in actuality, we’ll face some unusual outcomes. So, sure, this technique has its limitations. For instance, the net generator is able to producing poor outcomes, particularly with wavy strains. A part of the problem is because of a selected mixture of values the place the outcome will get scrambled, like utilizing an enormous worth for the border thickness in comparison with the scale:

For the opposite instances, it’s the problem associated to some rounding that can ends in misalignment and gaps between the waves:

That stated, I nonetheless assume the strategy we coated stays an excellent one as a result of it produces easy waves normally, and we are able to simply keep away from the dangerous outcomes by enjoying with totally different values till we get it good.

Wrapping up

I hope that after this text, you’ll no extra to fumble round with trial and error to construct a wavy form or sample. As well as to the net generator, you might have all the maths secrets and techniques behind creating any form of wave you need!

The article ends right here however now you might have a robust software to create fancy designs that use wavy shapes. Right here’s inspiration to get you began…

What about you? Use my on-line generator (or write the code manually should you already discovered all the maths by coronary heart) and present me your creations! Let’s have an excellent assortment within the remark part.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments