Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Fri, 15 Mar 2024 18:42:05 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.2 225069128 Creating Wavy Circles with Fancy Animations in CSS https://frontendmasters.com/blog/creating-wavy-circles-with-fancy-animations/ https://frontendmasters.com/blog/creating-wavy-circles-with-fancy-animations/#comments Fri, 15 Mar 2024 14:36:46 +0000 https://frontendmasters.com/blog/?p=1252 In a previous article, we created flower-like shapes using modern CSS (mask, trigonometric functions, etc). This article is a follow-up where we will create a similar shape and also introduce some fancy animations.

Article Series

Here is a demo of what we are building. Hover the image to see the animation

Cool right? If you check the HTML tab you won’t see a lengthy code structure. A single image element is all that we will be using to build that complex-looking effect. Don’t look at the CSS for now and let’s build this together.

Note: At the time of writing this, Firefox doesn’t support the animation take a look in a Chrome or Safari-based browser to read the article.

Creating The Shape

It’s highly recommended that you read the previous article because I will be building on top of it. I will be reusing many tricks and the starting point of this article will be the last demo of the previous one.

And here is a figure to remind you the mask composition used to create the shape.

a ring of circles uses "exclude" to knock them out, then "intersect" to make a cog like shape then "add" to make the final blob shape.

As you can see, a set of small circles is used in the “exclude” composition to create the inner curves, and another set of small circles is used in the “add” composition to create the outer curves. The idea is to move those circles in opposite directions to create and control our wavy circle.

Here is another figure to illustrate the trick

the differentd colored circles make the blob shape, and as they move around the blob changes shape.

The [1] above illustrates the initial shape where all the small circles are aligned in a way to create a bigger circle while touching each other. The red circles are the excluded ones and the blue circles are the added ones.

In [2] above we make the blue circles closer to the center while moving the red ones in the opposite direction. The result is weird because the circles no longer touch each other but if we increase their radius, we get a perfect result.

The idea is to move the circles and at the same time adjust their radius so they are always touching each other. Here is an interactive demo to better understand the movement of the circles. Use the slider to control the position of the circles.

Let’s write some code

Now that we have the geometry of shape in place, let’s translate this into code. It wasn’t an easy task at all. Finding the right formulas and translating everything into a CSS code was a bit tricky.

The first challenge is to find one variable that allows me to control the position of the small circles and at the same time their radius. I could have used multiple variables but having only one variable will make the shape easy to control as we only have to update one value and everything else will follow.

Initially, I thought about using a length variable which is logical since the radius can be expressed as a length and to move the circles I need a distance which is also a length. I wasn’t able to follow that root because finding the formulas and expressing them using CSS was almost impossible. Instead of a length I had to rely on an angle variable. It may sound wrong but it was indeed the right way to do it as I was able to find most of the formulas and write them using CSS.

Here is a figure to illustrate the angle I am referring to.

update to the angle between circles.

Let’s take two adjacent circles and draw a line between their center (illustrated in white). The [1] shows the initial shape where all the circles are perfectly aligned around the big circle. This will be the initial state so let’s consider we have an angle equal to 0deg. When we move the circles and get the shape in [2] the line will rotate a little and the angle of rotation will be our variable.

Don’t worry, I won’t start a boring geometry course. I just wanted to highlight the variable you need to adjust so you can visualize why it’s an angle and the angle of what. By the way, in the last interactive demo, you are adjusting that angle using the range slider.

Now, let’s take the shape of the previous article and try to introduce the new variable and adjust the different formulas. If you didn’t do it make sure to read that article to better understand what’s coming next. I might skip a few concepts I already explained in the previous article.

$n: 10; /* the number of circles/petals */

.flower {
  width: 400px;
  aspect-ratio: 1;
  --r: calc(50%/(1 + 1/sin(90deg/#{$n})));
  --g:/calc(2*var(--r)) calc(2*var(--r)) radial-gradient(50% 50%,#000 100%,#0000) no-repeat;
  $m1: ();
  $m2: ();
  @for $i from 1 through $n { 
    $m1: append($m1, 
     calc(50% + 50%*cos(360deg*#{$i/$n})) 
     calc(50% + 50%*sin(360deg*#{$i/$n}))
     var(--g), 
    comma);
    $m2: append($m2,
      calc(50% + 50%*cos((360deg*#{$i} + 180deg)/#{$n})) 
      calc(50% + 50%*sin((360deg*#{$i} + 180deg)/#{$n}))
      var(--g), 
    comma);
  }
  mask:
    #{$m1},
    radial-gradient(100% 100%,#000 calc(var(--r)/tan(90deg/#{$n})),#0000 0) intersect,
    radial-gradient(#000 0 0) exclude,
    #{$m2};
}

The Sass loop will generate two sets of circles: $m1 (the blue circles used with the add composition) and $m2 (the red circles used with the exclude composition).

First of all, we have to add the new angle variable

.flower {
   --a: 10deg;
}

Then we update the position and the radius of the circles:

$n: 10; /* the number of circles/petals */

.flower {
  --a: 10deg; 
  --s: 400px; /* shape size */

  width: var(--s);
  aspect-ratio: 1;
  --r: calc(var(--s)/(2 + 2/sin(90deg/#{$n})));
  --R: f(--r,--a);
  --g:/var(--R) var(--R) radial-gradient(50% 50%,#000 100%,#0000) no-repeat;
  $m1: ();
  $m2: ();
  @for $i from 1 through $n { 
    $m1: append($m1, 
     calc(50% + (50% - f1(--r,--a))*cos(360deg*#{$i/$n})) 
     calc(50% + (50% - f1(--r,--a))*sin(360deg*#{$i/$n}))
     var(--g), 
    comma);
    $m2: append($m2,
      calc(50% + (50% + f2(--r,--a))*cos((360deg*#{$i} + 180deg)/#{$n})) 
      calc(50% + (50% + f2(--r,--a))*sin((360deg*#{$i} + 180deg)/#{$n}))
      var(--g), 
    comma);
  }
  mask:
    #{$m1},
    radial-gradient(100% 100%,#000 calc(var(--r)/tan(90deg/#{$n})),#0000 0) intersect,
    radial-gradient(#000 0 0) exclude,
    #{$m2};
}

You might ask what’s going on with the f(), f1(), and f2(). It’s not a new CSS feature so don’t try to google it. I am using them as placeholders to make the code look easy and to better understand the logic.

The circles need a new radius so I am defining --R where its value is a function of the old radius --r and the new angle variable --a. As for the positions, the first set of circles needs to get closer to the center so I am reducing their distance 50% - f1() while the second set needs to get far so I am increasing their distance 50% + f2().

I won’t get into the math details of each function otherwise this will turn into a boring geometry course. All you have to do is to understand the main logic and at the end, you can easily adjust the shape using CSS variables.

Also, note the use of the --s variable to control the size. With this new configuration, I am not able to rely on percentage so I need to have the size as a variable and use it to calculate the radius --r.

Here is the full demo where you can play with the different values to control the shape.

Introducing The Image Element

Let’s now consider an <img> element instead of a <div>.

It works fine but our goal is to have the image within the shape like the first example. To do this, we add some padding (or border) to leave space for the background and also add border-radius to round the image.

Now it’s perfect! I am using a padding value equal to 2.2*var(--r) but there is no particular logic behind it. It’s what gives me something that looks good to me. Feel free to update it if you want to increase or decrease the space around the image.

Adding The Animation

Let’s move to the interesting part which is the animation. We will first adjust the shape on hover and this is the easiest part because we already did the hard job by finding one variable to control the shape. All we have to do is to update that variable on hover.

img {
  --a: 28deg;
}
img:hover {
  --a: 10deg;
}

The above will simply change the shape but will not give us a smooth animation it is because by default we cannot animate CSS variables (custom properties). To do this we need to register them using @property.

@property --a {
  syntax: "<angle>";
  inherits: true;
  initial-value: 0deg;
}

Then add a transition like below

img {
  transition: --a .3s
}

Now we have a perfect hover effect!

Let’s tackle the rotation. It’s clear that we cannot rotate the whole element as the image needs to remain straight and only the shape needs to rotate. To do this, we will introduce a new angle variable and use it within the formulas that define the position of the circles.

If you look at the Sass loop that generates the code of the circles, you will notice that the increment $i is used to define an angle, and this angle is used to correctly place each circle. If we update that angle, we update the position. The idea is to update the angle of all the circles with the same value so they all move the same way to simulate a rotation.

In the demo above, you will see I am registering a new variable and applying an animation to it

@property --o {
  syntax: "<angle>";
  inherits: true;
  initial-value: 0deg;
}
img {
  animation: rotate 20s infinite linear;
}
@keyframes rotate {
  to { --o: 360deg; }
}

Then I am using that variable within the Sass loop to update the angle of the circles. Instead of 360deg*#{$i/$n}, we use 360deg*#{$i/$n} + var(--o) and instead of (360deg*#{$i} + 180deg)/#{$n} we use (360deg*#{$i} + 180deg)/#{$n} + var(--o).

The final touch is to increase the speed of the rotation on hover. For this, I am going to introduce the animation-composition property. It’s a pretty new propery so you may have not heard about it, but it’s a powerful property that I invite you to explore. Plus the browser support is pretty good.

I will update the code of the animation like below:

img {
  animation: 
    rotate 20s infinite linear,
    rotate 20s infinite linear paused;
  animation-composition: add
}
img:hover {
  animation-play-state: running;
}

I am applying the same animation twice while making the second one paused. On hover, both animations are running. Let’s have a look at the definition of animation-composition: add

The animation-composition CSS property specifies the composite operation to use when multiple animations affect the same property simultaneously.

Then:

add
The effect value builds on the underlying value of the property. This operation produces an additive effect.

We are using the same animation so we are affecting the same property (the variable --o) and the use of add will create an additive effect. It means that the element will rotate faster when both animations are running.

Try it!

The concept of animation-composition is not easy to grasp at first glance, but imagine that you add an animation on the top of another one. The first animation is rotating the element then we add another animation that will also rotate the element. If you rotate an element that is already rotating then you get a faster rotation. We can also decrease the rotation speed using the same technique. This time, the second animation needs to apply an opposite rotation using the reverse keywords.

Conclusion

We are done! We created a complex-looking effect using only CSS and a single HTML element. It was a good opportunity to explore modern CSS features such as mask, trigonometric functions, @property, etc

You are probably thinking it’s a bit too much, right? What’s the point of introducing all this complexity for a fancy effect that you will probably never use? The goal is not really to build the effect and use it but to push the limit of CSS and explore new features. In the end, you have a bunch of CSS tricks that you can use elsewhere.

We learned how to use mask-composite. We learned how to animate CSS variables using @property. We played with gradients. We discovered the animation-composition property. etc. One day, you will for sure need to use those CSS tricks!

Article Series

]]>
https://frontendmasters.com/blog/creating-wavy-circles-with-fancy-animations/feed/ 1 1252
Creating Flower Shapes using CSS Mask & Trigonometric Functions https://frontendmasters.com/blog/creating-flower-shapes-using-css-mask-trigonometric-functions/ https://frontendmasters.com/blog/creating-flower-shapes-using-css-mask-trigonometric-functions/#respond Thu, 29 Feb 2024 15:04:12 +0000 https://frontendmasters.com/blog/?p=1021 Creating unusual shapes is always a fun exercise and a good way to practice your CSS skills. One might argue that SVG is better for this job, but nowadays we have a lot of new CSS tricks that allow us to create shapes with a clean and optimized code. Through this two-article series, we will explore what can be done with CSS nowadays.

Article Series

In this article, we are going to create flower-like shapes. We are going to rely on modern features like mask, trigonometric functions, CSS variables, etc.

four flower-like shapes with purple to pink gradients. two of them have petals and two of them have spikes. two of them are filled and two of them are outlined.

Before we start, you can take a look at my online generator for flower shapes to get an overview of what we are building here. You can easily define your settings and get the CSS code in no time. Some of the code we will be writing can be complex so it’s always good to have a generator to make our life easy. That said I invite you to keep reading to understand the logic behind the code you are copying and be able to tweak it if needed. 

The Geometry of A Flower Shape

The structure of the flower shape can be seen as small circles placed around a bigger one. 

Consider that the small circles touch each other without overlapping each other. This will make the calculation a bit challenging as we will need accurate formulas. Turns out this is a perfect use case for trigonometric functions in CSS.

The shape can be controlled using 2 variables; the number of small circles (N) and their radius (R). From there we can identify the size of the whole shape and the radius of the big circle.

Here is a figure to illustrate some of the values and from where we can extract the different formulas.

geometry of the circles showing the radius comparisons.

I won’t start a boring geometry course so I will jump straight to the formulas we need to use. The size of the element is equal to

2 * (X + R) = 2 * (R/sin(180deg/N) + R) = 2 * R * (1 + 1/sin(180deg/N))

and the radius of the big circle is equal to

C  = R/tan(180deg/N)

We have all that we need to start writing some code.

Coding The Shape

The main challenge is to rely on a single element. We are not going to consider a complex HTML structure where each circle is a different element. Instead, we will only use one element (and no pseudo-elements either!)

I mentioned mask, we’ll be using that, and gradients will allow us to do the shape drawing we want to do. Since it’s all about circles we are going to use radial-gradient(). We will also use a bit of Sass (for the looping feature) to control the number of circles. The number of gradients will depend on the number of circles and with Sass we can write a loop to generate the needed gradients.

Let’s start by setting the different variables and the shape size:

$n: 12; /* the number of circles/petals */

.flower {
  --r: 30px; /* the radius of the small circles */
  width: calc(2*var(--r)*(1 + 1/sin(180deg/#{$n})));
  aspect-ratio: 1;
  mask: radial-gradient(#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0);
  background: /* you background coloration */
}

Nothing fancy so far, we simply translated the previous formulas using code. I also added the big circle so all that we are missing is the small ones. The Sass code to generate them will look like this:

$m: (); /* empty variable */
@for $i from 1 through ($n) { /* loop through the number of circles*/
  $m: append($m, 
    radial-gradient(50% 50%,#000 100%,#0000) no-repeat
    x y / calc(2*var(--r)) calc(2*var(--r)), 
  comma);
}

--r defines the radius so the size of each gradient will be equal to calc(2*var(--r)). Then we need to identify the position of each gradient (the x y).

Here as well, we need to consider some geometry formulas

x = calc(50% + 50%*cos(360deg*#{$i/$n})) 
y = calc(50% + 50%*sin(360deg*#{$i/$n}))

The final code will be:

$n: 6; /* the number of circles/petals */

.flower {
  --r: 30px; /* the radius of the small circles */
  width: calc(2*var(--r)*(1 + 1/sin(180deg/#{$n})));
  aspect-ratio: 1;
  $m: (); /* empty variable */
  @for $i from 1 through ($n) { /* loop through the number of circles*/
    $m: append($m, 
      radial-gradient(50% 50%,#000 100%,#0000) no-repeat
      calc(50% + 50%*cos(360deg*#{$i/$n})) 
      calc(50% + 50%*sin(360deg*#{$i/$n}))
     / calc(2*var(--r)) calc(2*var(--r)), 
    comma);
  }
  mask: radial-gradient(#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0),{$m};
  background: /* you background coloration */
}

Note how the mask property takes the value generated using Sass in addition to the gradient that creates the big circle.

Our shape is done!

The size of the shape is controlled by the radius of the small circles but we can do the opposite which is probably more convenient since we generally want to control the width/height of our element.

.flower {
  --w: 200px; /* the size */
  --r: calc(var(--w)/(2*(1 + 1/sin(180deg/#{$n}))));
  width: var(--w);
  /* same as before */
}

We can even optimize the previous code a little and get rid the of --w variable. The latter is defining the width/height of the element and gradients can access such value using percentages we can write the code like below:

$n: 12; /* the number of circles/petals */

.flower {  
  width: 300px; /* the size */
  --r: calc(50%/(1 + 1/sin(180deg/#{$n}))); /* using percentage instead of --w */
  aspect-ratio: 1;
  $m: (); /* empty variable */
  @for $i from 1 through ($n) { /* loop through the number of circles*/
    $m: append($m, 
      radial-gradient(50% 50%,#000 100%,#0000) no-repeat
      calc(50% + 50%*cos(360deg*#{$i/$n})) 
      calc(50% + 50%*sin(360deg*#{$i/$n}))
     / calc(2*var(--r)) calc(2*var(--r)), 
    comma);
  }
  mask: radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0),#{$m};
}

Now by adjusting the width you control the size of the whole shape. Here is an interactive demo where you can resize the element and see how the shape adjusts automatically. Try it below with the resizer handle on the bottom right of the box:

Voilà! We did a nice flower shape without hack or complex code and you can easily control it by adjusting a few variables. You either use the above code or you consider my online generator to get the generated CSS without the variables and Sass.

Inverting the shape

Let’s try a different shape this time. It’s somehow the invert of the previous one where the circular part is going inside instead of outside. Well, a figure worth a thousand words.

spiky flower shape with purple to orange coloring

The code to get the above shape is almost the same as the previous one. We are going to introduce an extra gradient and play with mask-composite. The idea is to cut the small circles from the bigger one.

Here is a figure to illustrate the process:

multiple masking techniques used, exclude and include, to come to the final shape

We will perform two compositions. For the first one, we consider a gradient that fills the whole area and the set of small circles. We “exclude” one from another to get a shape where the small circles are now holes. In other words, the small circles are now the invisible part of our shape.

From there, we consider the gradient that creates the big circle and we do an “intersect” this time to show only the middle area and get our final shape.

The code of mask will look like the below

mask: 
 radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0) intersect,
 radial-gradient(#000 0 0) exclude,
 #{$m};

And here is the one of the previous shape to compare both

mask: 
 radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0),
 #{$m};

The only difference is an extra gradient (the one that will fill the whole shape) and the value of mask-composite. And since the remaining code is also the same, we can introduce a variable to control which one to use:

$n: 12; /* the number of circles/petals */

.flower {
  width: 300px;
  aspect-ratio: 1;
  --r: calc(50%/(1 + 1/sin(180deg/#{$n})));
  $m: (); /* empty variable */
  @for $i from 1 through ($n) { /* loop through the number of circles*/
    $m: append($m, 
      radial-gradient(50% 50%,#000 100%,#0000) no-repeat
      calc(50% + 50%*cos(360deg*#{$i/$n})) 
      calc(50% + 50%*sin(360deg*#{$i/$n}))
     / calc(2*var(--r)) calc(2*var(--r)), 
    comma);
  }
  mask: radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0) var(--alt,),#{$m};
}
.alt {
  --alt: intersect,radial-gradient(#000 0 0) exclude;
}

The var(--alt,) will default to nothing when the variable is not specified and we get the code of the first shape. By adding the extra gradient and the composition values, we get the second shape. Two different shapes with almost the same code.

The Border-Only Version

Now let’s tackle the border-only version of the previous shapes. We are also going to rely on mask-composite and the same code structure.

First of all, let’s introduce a new variable to control the border thickness and update the code that generates the small circles.

$n: 12; /* the number of circles/petals */

.flower {
  --b: 10px; /* the border thickness*/

  width: 300px;
  aspect-ratio: 1;
  --r: calc(50%/(1 + 1/sin(180deg/#{$n})));
  $m: (); /* empty variable */
  @for $i from 1 through ($n) { /* loop through the number of circles*/
    $m: append($m, 
      radial-gradient(50% 50%,#0000 calc(100% - var(--b)),#000 0 100%,#0000) no-repeat
      calc(50% + 50%*cos(360deg*#{$i/$n})) 
      calc(50% + 50%*sin(360deg*#{$i/$n}))
     / calc(2*var(--r)) calc(2*var(--r)), 
    comma);
  }
  mask: #{$m};
}

Nothing complex so far. Instead of full circles, we are getting a border-only version. This time we don’t want them to touch each other but rather overlap a bit to have a continuous shape.

We need to increase the radius a little from this

--r: calc(50%/(1 + 1/sin(180deg/#{$n})));

To this:

--r:calc((50% + var(--b)/(2*sin(180deg/#{$n})))/(1 + 1/sin(180deg/#{$n})));

Again some geometry stuff but you don’t really need to accurately understand all the formulas. I did the hard work to identify them and you only need to understand the main trick. In the end, all you have to do is update a few variables to control the shape or get the code from my generator.

The result so far:

Now, we use mask-composite with another gradient to hide some parts and get our final shapes. Here is a figure to illustrate the process for both shapes.

For the first shape:

And for the second one:

In both cases, I am introducing an extra gradient that will intersect with the small circles. The difference between each shape is the coloration of the extra gradient. In the first case, we have transparent inside and filling outside and for the second case, we have the opposite.

/* first shape */
mask:
 radial-gradient(100% 100%,#0000 calc(var(--r)/tan(180deg/#{$n}) - var(--b)/(2*tan(180deg/#{$n}))),#000 0) intersect, 
 #{$m};
/* second shape */
mask:
 radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n}) - var(--b)/(2*tan(180deg/#{$n}))),#0000 0) intersect, 
 #{$m};

And here is the full code with both variations:

If you have some trouble visualizing how mask-composite works, I invite you to read the crash course written by Ana Tudor where you will find a more in-depth exploration.

One More Shape

Another flower? Let’s go!

blob like shape, sort of like a gear with smoothed over cogs.

This time, it’s your turn to figure out the code. Consider this as a small piece of homework to practice what we have learned together. As a hint, here is a figure that illustrates the mask-composite you need to perform, or maybe you will figure out another idea! If so don’t hesitate to share it in the comment section

Here is the code of my implementation but make a try before checking it so you can compare your method with mine. Take the time to study this last example because it will be the starting point of our second article.

Conclusion

I hope you enjoyed this little experimentation using CSS mask and trigonometric functions. You are probably not going to use such shapes in a real project but creating them is a good way to learn new features. If you have to remember one thing from this article it’s the use of mask-composite. It’s a powerful property that can help you create complex shapes.

It is worth noting that since we are using mask, we can easily apply our shape to image elements.

Don’t forget to bookmark my flower shapes generator so you can easily grab the code whenever you need it. I also have more CSS generators that invite you to check. Most of them rely on CSS mask as well and I have a detailed article linked to each one.

I will close this article with a few mesmerizing animations involving some flower shapes.

Article Series

]]>
https://frontendmasters.com/blog/creating-flower-shapes-using-css-mask-trigonometric-functions/feed/ 0 1021