Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Fri, 01 Nov 2024 14:24:15 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 CSS Fan Out with Grid and @property https://frontendmasters.com/blog/css-fan-out-with-grid-and-property/ https://frontendmasters.com/blog/css-fan-out-with-grid-and-property/#comments Wed, 09 Oct 2024 13:27:37 +0000 https://frontendmasters.com/blog/?p=4128 A “fan out” is an expanding animation where a group of items appear one after another, next to each other, as though they were spread out from a stack. There’s usually a subtle bounciness in the reveal.

The effect is customarily achieved by timing and positioning each of the items individually with very specific hard set values. That can be an awful lot of work though. We can make things a bit easier if we let the items’ parent container do this for us. Here’s a result of doing it this way: 

UPDATE: This article has been updated to now include the animation of the grid items’ height, to produce an overall smoother transition effect. The previous version of this article didn’t cover that. 

For HTML, there’s a group of items (plus an empty one — I will explain later why it’s there), bookended by two radio controls to prompt the opening and closing of the items respectively.

<section class="items-container">
  <p class="items"><!--empty--></p>
  <label class="items close">
    Close the messages<input type="radio" name="radio">
  </label>
  <p class="items">Alert from Project X</p>
  <p class="items">&#x1F429; Willow's appointment at <i>Scrubby's</i></p>
  <p class="items">Message from (-_-)</p>
  <p class="items">NYT Feed: <u>Weather In... (Read more)</u></p>
  <p class="items">6 more items to check in your vacation list!</p>
  <label class="items open">
    Show the messages<input type="radio" name="radio">
  </label>
</section>

We need a grid container for this to work, so let’s turn the <section>, the items’ container element, into one. You could use a list or anything you feel is semantically appropriate.

.items-container {
  display: grid; 
}

Now create an Integer CSS custom property with a value same as the number of items inside the container (including the open and close controls, and the empty item). This is key to implement the revealing and hiding of the items, sequentially, from within the grid container’s style rule.

Also, register another CSS custom property of data type length that’ll be used to animate each item’s height during the opening and closing of the control, for a smoother execution of the overall action. 

@property --int {
  syntax: "<integer>";
  inherits: false;
  initial-value: 7;
}

@property --hgt {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

Use the now created --int and --hgt properties to add that many grid rows of zero height in the grid container. 

.items-container {
  display: grid; 
  grid-template-rows: repeat(calc(var(--int)), var(--hgt));  
}

When directly adding --int to repeat() it was producing a blotchy animation in Safari for me, so I fed it through calc() and the animation executed well (we’ll look into the animation in a moment). However, calc() computation kept leaving out one item in the iteration, because of how it computed the value 0. Hence, I added the empty item to compensate the exclusion. 

If Safari did not give me a blotchy result, I would’ve not needed an empty item, --int’s initial-value would’ve been 6, and grid-template-rows’s value would’ve been just repeat(var(--int), 0px). In fact, with this set up, I got good animation results both in Firefox and Chrome. 

In the end though, I went with the one that uses calc(), which provided the desired result in all the major browsers. 

Let’s get to animation now:

@keyframes open { to { --int: 0; --hgt:60px;} }
@keyframes close { to { --int: 6; --hgt:0px;} } 
.item-container {
  display: grid; 
  grid-template-rows: repeat(calc(var(--int)), var(--hgt)); 
  &:has(.open :checked) {
  /* open action */
    animation: open .3s ease-in-out forwards;
    .open { display: none; }
  }
  &:has(.close :checked) {
  /* close action */
    --int: 0;
    --hgt: 60px;
    animation: close .3s ease-in-out forwards;
  }
}

When the input is in the checked state, the open keyframe animation is executed, and the control itself is hidden with display: none

The open class changes --int’s value from its initial-value, 7, to the one set within the @keyframes rule (0), over a set period (.3s). This decrement removes the zero height from each of the grid row, one by one, thus sequentially revealing all the items in .3s or 300ms. Simultaneously, --hgt’s value is increased to 60px from its initial 0px value. This expands each item’s height as it appears on the screen. 

When the input to hide all the items is in the checked state, the close keyframe animation is executed, setting --int’s value to 0 and --hgt’s value to 60px.

The close class changes the now 0 value of --int to the value declared in its rule: 7. This increment sets a zero height to each of the grid row, one by one, thus sequentially hiding all the items. Simultaneously, --hgt’s value is decreased to 0px. This shrinks each item’s height as it disappears from the screen. 

To perform the close action, instead of making a unique close animation, I tried using the open animation with animation-direction: reverse. Unfortunately, the result was jerky. So I kept unique animations for the open and close actions separately.

Additionally, to polish the UI, I’m adding transition animations to the row gaps and text colors as well. The row gaps set cubic-bezier() animation timing function to create a low-key springy effect. 

.scroll-container {
  display: grid; 
  grid-template-rows: repeat(calc(var(--int)), 0px); /* serves the open and close actions */
  transition: row-gap .3s .1s cubic-bezier(.8, .5, .2, 1.4);
  &:has(.open :checked) {
    /* open action */
    animation: open .3s ease-in-out forwards;
    .open { display: none; }
    /* styling */
    row-gap: 10px;
    .items { color: rgb(113 124 158); transition: color .3s .1s;}
    .close { color: black }
  }
  &:has(.close :checked) {
    /* close action */
    --int: 0;
    animation: close .3s ease-in-out forwards;
    /* styling */
    row-gap: 0;
    .items { color: transparent; transition: color .2s;}
  }
}

When expanded, the row gaps go up to 10px and the text color comes in. When shrinking, the row gaps go down to 0 and the text color fades out to transparent. With that, the example is complete! Here’s the Pen once more:

Note: You can try this method with any grid compositions — rows, columns, or both.

Further Reading

]]>
https://frontendmasters.com/blog/css-fan-out-with-grid-and-property/feed/ 3 4128
How to Make a CSS Timer https://frontendmasters.com/blog/how-to-make-a-css-timer/ https://frontendmasters.com/blog/how-to-make-a-css-timer/#comments Wed, 29 May 2024 16:40:25 +0000 https://frontendmasters.com/blog/?p=2424 There are times when we can use a timer on websites. 😉

Perhaps we want to time a quiz or put pressure on a survey. Maybe we are just trying to making a dramatic countdown after a user has done something great like successfully booking a concert ticket. We could be trying to build a micro time management tool (think Pomodoro). Or it could just be an alternative to a spinner UI.

When those situations come up, I have no qualms employing JavaScript which is probably a more powerful tool for this sort of thing generally. And yet! CSS substitutes are just as fun and efficient when the simplest option is the best one. Some email clients these days are highly CSS capable, but would never run JavaScript, so perhaps that situation could be an interesting progressive enhancement.

Let’s take a look at what it takes to cook up a CSS timer. We’ll use some modern CSS tech to do it. The ingredients?

  1. CSS Counters
  2. @property
  3. pseudo elements
  4. @keyframes
  5. A little Celtic sea salt to taste

To get started, fire up a (Code)Pen and keep it warm. Below is the demo we’ll be working towards (later, I’ll show some stylized examples):

There are three main requirements for our CSS Timer:

  1. A number that can decrement from 5 to 0
  2. A way to time five seconds, and decrement the number in each
  3. A way to display the decreasing number on page 

The Number

For our first requirement, the update-able number, we’ll use @property to create a custom property that will hold a value of type <integer>

Note: Integer numbers can be zero, or a positive or negative whole number. If you want numbers with decimal points, use <number>, which holds a real number. 

@property --n {
  syntax: "<integer>";
  inherits: false;
  initial-value: 0;
}

The Counting

For tracking seconds, while decreasing the number, we go to @keyframes animation.

@keyframes count {
  from { --n: 5; }
  to   { --n: 0; }
}

The animation function is put to action with the animation property.

.timer:hover::after {
  animation: 5s linear count;
}

Here’s what’s happening: 

When we register a custom property for a specific value type, <integer>, <percentage>, or <color>, for instances, the browser knows that the property is created to work with that specific type of value. 

With that knowledge the browser confidently updates the custom property’s value in the future, even throughout an animation. 

That’s why our property --n can go from 5 to 0 within an animation, and since the animation is set for five seconds, that’s essentially counting from five to zero over a period of five seconds. Hence, a timer is born. 

But there’s still the matter of printing out the counted numbers onto the page. If you hadn’t noticed earlier, I’d assigned the animation to a pseudo-element, and that should give you a clue for our next move — content

The Display

The property, content, can display contents we have not yet added to the HTML ourselves. We generally use this property for a variety of things, because this accepts a variety of values — images, strings, counters, quotation marks, even attribute values. It doesn’t, however, directly takes a number. So, we’ll feed it our number --n through counter

A counter can be set with either counter-reset or counter-increment. We’ll use counter-reset. This property’s value is a counter name and an integer. Since counter-reset doesn’t correctly process a CSS variable or custom property for an integer yet, but does accept calc(), the calc() function becomes our Trojan Horse, inside of which we’ll send in –n. 

.timer:hover::after {
  animation: 5s linear count;
  animation-fill-mode: forwards; 
  counter-reset: n calc(0 + var(--n));
  content: counter(n);
}

That is:

  1. Our animate-able number, --n, is first fed to calc() 
  2. calc() is then fed to counter()
  3. The counter() in turn is given to content, finally rendering --n on the page. 

The rest is taken care of by the browser. It knows --n is an integer. The browser keeps up with animation changing this integer from 5 to 0 in five seconds. Then, because the integer is used in a content value, the browser displays the integer on the page as it updates.

At the end of the animation, the animation-fill-mode: forwards; style rule prevents the timer from reverting back to the initial --n value, zero, right away. 

Once again, here’s the final demo:

For design variants you can count up or down, or play with its appearance, or you can combine this with other typical loader or progress designs, like a circular animation

At the time of writing, Firefox is the only missing browser support for @property, but they have announced an intent to ship, so shouldn’t be long now. For support reference, here’s the caniuse.com page for @property

CSS Custom Properties can also be set and updated in JavaScript. So, if at some point you would like to be able to update the property in JavaScript, just about with any other CSS property, you can do it using the setProperty() function. And if you wish to create a new custom property in JavaScript, that can be done with registerProperty(). The other direction, if you wanted to let JavaScript know a CSS animation has completed, you could listen for the animationend end event.

If you’re really into this sort of thing, also check out Yuan Chuan’s recent Time-based CSS Animations article.

]]>
https://frontendmasters.com/blog/how-to-make-a-css-timer/feed/ 1 2424
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.

You can also take a look at my online generator where you can easily generate the CSS code for those wavy circles.

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.

As you can see, a set of small circles is used in the “subtract” 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.

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

If you want to dig into the math, check this question on StackExchange Mathematics. I struggled with some of the formulas so I had to ask some math gurus.

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 .4*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