Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Wed, 24 Jul 2024 11:55:17 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.1 225069128 Clip Pathing Color Changes https://frontendmasters.com/blog/clip-pathing-color-changes/ https://frontendmasters.com/blog/clip-pathing-color-changes/#respond Tue, 23 Jul 2024 17:29:41 +0000 https://frontendmasters.com/blog/?p=3103 This is a nice post from Emil Kowalski on usage of the clip-path property in CSS. I’ve always liked clip-path. Maybe it’s because it’s such a sharp knife. When you clip an element, it’s clipped, yo. There isn’t a lot of nuance to it, it does what it does. But moreso, I think I like the connection to SVG (oh, hey, by the way, I just made my old book Practical SVG entirely free). The value that you give clip-path is stuff like circle(), polygon(), path(), etc — the primitive shapes of SVG.

In Emil’s post, my favorite example is a navigation bar where a “pill” shape animates from one navigation item to another when they are clicked. The pill is a different background color, and so the text color also changes. (If you’re over 100 years old like me, we used to call this kind of thing “lava lamp” navigation 👴).

I would guess most people would assume what is happening here is an extra element set behind the links that moves position to underneath the newly active links. You could do it that way, but there is a minor aesthetic issue with it. Because the background-color is changing here, the text also needs to change appropriately (from dark to light here). You could change that color instantly, but that will look weird like it’s changing too early. You could set a transition on it, but you’ll never get the fade to look quite right, especially as it has to go through an awkward gray color.

Essentially, you’ll never get a state like this:

This ain’t gonna happen with an underlay element alone.

See how the text is half-light and half-dark mid-animation when the highlight blue pill moves from one to another? That’s a lovely effect that makes this feel very polished and smooth. This idea first came from a tweet by Paco. Like Emil says:

You might say that not everyone is going to notice the difference, but I truly believe that small details like this add up and make the experience feel more polished. Even if they go unnoticed.

Agreed.

In Emil’s post, it’s done with React. That’s totally fine, but I figured I’d make a vanilla one for y’all here:

Here’s how this works:

  1. There is one set of semantic HTML navigation.
  2. If JavaScript executes, it duplicates the nav (we’ll need two) but ensures the duplicate is hidden for screen readers.
  3. The duplicate is placed exactly on top of the original (it’s the “blue” one) and can’t directly be clicked (i.e. pointer-events: none;)
  4. A clip-path is set that highlights one of the navigation items in particular by clipping the entire duplicate except one link.
  5. As links are clicked, the clip-path is changed using positional math, highlighting the new one. Also high-five for the round keyword that can be used with inset() for rounded corners on inset rectangles.
  6. The clip-path animates, thanks to a basic CSS transition.

I think it’s cool as heck that it all comes together that cleanly.

It’s also a nice touch that the new clip-path positions are calculated based on their page position, meaning that there are really no magic numbers here. If we add navigation items or change them, this code will be resilient and it will all still work. And if none of this JavaScript runs at all, no big deal.

]]>
https://frontendmasters.com/blog/clip-pathing-color-changes/feed/ 0 3103
Single CSS Keyframe Tricks are Magic https://frontendmasters.com/blog/single-css-keyframe-tricks-are-magic/ https://frontendmasters.com/blog/single-css-keyframe-tricks-are-magic/#comments Tue, 18 Jun 2024 13:31:23 +0000 https://frontendmasters.com/blog/?p=2758 What happens with a CSS @keyframe animation like this when called?

@keyframes pulse {
  50% {
    scale: 1.5;
  }
}

There is only one “keyframe” there at 50%. So what happens at 0% through the animation? The scale property is… whatever it already was. And at 100%? Back to whatever it already was. Assuming the default scale of 1, it will grow the element then shrink back down again. So little code!

David East and Adam Argyle dig into this in the video Single CSS keyframe tricks are magic and have a great demo.

]]>
https://frontendmasters.com/blog/single-css-keyframe-tricks-are-magic/feed/ 1 2758
One of the Boss Battles of CSS is Almost Won! Transitioning to Auto https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/ https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/#comments Wed, 12 Jun 2024 17:02:05 +0000 https://frontendmasters.com/blog/?p=2550 TL;DR: The experimental CSS function calc-size(auto) can be used so that transitions and animations can go from zero (0) to this value. But that is unlikely to be the final syntax! ⚠️ So be forewarned.

Let’s properly understand this. Lemme ask you something: how many pixels tall is this list below?

<ul>
  <li>How</li>
  <li>Tall</li>
  <li>Am I?</li>
</ul>

Trick question. You can’t possibly know (from CSS, anyway). It depends on the font family, font size, layout choices, user preferences and overrides, screen size, and much more. Because CSS doesn’t know, it can’t animate to it. It feels silly because c’mon it’s right there on the page, but that’s just the way it is.

For many, many years, one of the most common wishes for CSS developers is some way to animate an element from hidden (or newly added to the page) as zero-height to whatever it’s natural intrinsic size is. “Animate to auto” or “transition to auto” is how it’s often talked about. The desire is pretty straightforward:

.element {
  height: 0; /* or block-size */
  transition: height 0.2s ease-in-out;

  &.open {
    /* nope, sorry, transition will not happen */
    height: auto;
  }
}

This kind of thing is essentially an open or close animation, which is the most common animation of all. Check the boxes below to see how only the box with a known height can have its height animated:

There are work arounds, like Just Using JavaScript™️ (e.g. measure the size off screen then animate to that). Or, animating the max-height instead, which sort of works but it messes with the timing as it’s likely part of the animation will be animating to a number too-high or too-low.

To be fair, we sort of got this when we got View Transitions. (See this example). But same-page View Transitions require JavaScript and are not as ergonomic as just using the basic CSS.

CSS is poised to beat this boss battle soon, which is an incredible thing to see. It needs to be able to do two things:

  • Animate from zero-height (or being just-added to the DOM) to the intrinstic (auto) size. Mostly height / block-size, but the other direction is helpful too.
  • Animate from an intrinsic (auto) size back to zero.

As I write, there is a (very experimental) version of this working in Chrome Canary. It’s all solved with one little line:

.element {
  height: 0; /* or block-size */
  transition: height 0.2s ease-in-out;

  &.open {
    /* works now! 🎉 */
    height: calc-size(auto);
  }
}

If you’ve got a copy of Chrome Canary with Experimental Web Platform Features flag on you’ll see this work right now!

Fair Warning: This is Probably Going to Break

I warned you. This is experimental stuff. I’m told that calc-size() probably is not going to be how this all ends up. It may be that there is an “opt-in” property that has to be set (then cascades) for this to work. It’s all early and unclear, and that’s fine because that’s how the best solutions happen.

A Real World Example: Dropdowns

Dropdown menus are a good example as they can have in them a different number of elements and thus have an unknown height, and yet you may want to animate them open. In the demo below, dropdown menus are exactly what I’ve made. The submenus are hidden by virtue of being absolutely positioned and of zero height. I find you generally don’t want to display: none a submenu as then it makes tabbing through the menu difficult or impossible.

The CSS is heavily annotated above to explain each interesting bit. Here’s a video of it all working Chrome Canary:

Note that this menu intentionally doesn’t use display: none to hide the submenus for accessibility reasons. If you do need to also transition an element from the display: none state (or just being added to the DOM for the first time), that adds more complication. But amazingly, modern CSS is up for that job also, as we’ll see next.

Transitioning from display: none;

Let me just show you the code:

.element {
  /* hard mode!! */
  display: none;

  transition: height 0.2s ease-in-out;
  transition-behavior: allow-discrete;

  height: 0; 
  @starting-style {
    height: 0;
  }

  &.open {
    height: calc-size(auto);
  }
}

The transition-behavior: allow-discrete; (mind-bending name that I do not understand) allows the display property to swap where it updates during the transition. Instead of the normal behavior of changing immediately (thus preventing any animation) it changes at the end (and vice-versa when animating from hidden).

Then we also need @starting-style here, which duplicates the “closed” styling. That seems awfully weird too, but this is how it’s going to work (there is real browser support for this). A way that helps me think about it is that when display: none is in use, none of the other styles are really applied to the element, it’s just not there. When the open class is applied here, all those styles are immediately applied. It had no prior state. The open state is the only state. So @starting-style is a way to force in a prior state.

Adam Argyle was experimenting with some globally applied styles using @starting-style with some generic styles such that any element appearing on the page gets a bit of a scale up and fade in.

The idea started like this:

* {
  transition: opacity .5s ease-in;
  @starting-style { opacity: 0 }
}

And then with a bit more nuance and care ended up like this:

@layer {
  * {
    @media (prefers-reduced-motion: no-preference) {
      transition: 
        opacity .5s ease-in, 
        scale   .5s ease-in,
        display .5s ease-in;
      transition-behavior: allow-discrete;
    }

    @starting-style { 
      opacity: 0; 
      scale: 1.1; 
    }

    &[hidden],
    dialog:not(:modal), 
    &[popover]:not(:popover-open) { 
      opacity: 0;
      scale: .9;
      display: none !important; 
      transition-duration: .4s;
      transition-timing-function: ease-out;
    }
  }
}

You might also enjoy watching Zoron Jambor’s video about all this if you like taking in information that way!

]]>
https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/feed/ 6 2550
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
Compare Web Animation Techniques https://frontendmasters.com/blog/compare-web-animation-techniques/ https://frontendmasters.com/blog/compare-web-animation-techniques/#respond Wed, 07 Feb 2024 15:23:34 +0000 https://frontendmasters.com/blog/?p=746 Looks like the nice folks at Sparkbox put this animation comparison page together a few years ago. I love how extremely comprehensive it is. Of course we can animate things on the web with CSS, and there is a whole JavaScript API for it, and loads of JavaScript libraries all with different characteristics, but don’t forget things like video or a GIF!

Being a senior developer means having a strong sense of the paths you could go down, so you can make educated choices.

]]>
https://frontendmasters.com/blog/compare-web-animation-techniques/feed/ 0 746