- Advanced CSS Animation Using Cubic bezier()
- Originally written by Temani Afif
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: Hoarfroster
- Reviewer:
When working with complex CSS animations, we tend to use a lot of declarations to create extension @keyframes. While I’d like to talk about a few tricks that might help make building complex animations easier while still doing it all with native CSS:
- Various animations;
- Timing function;
The first is more widely used and familiar, but the second is less common. This is probably for good reason — linking animations with commas is easier than understanding the various timing functions available to us and what they do. There is a particularly neat timing feature that gives us complete control over creating custom timing features. That will be cubic- Bezier (), and in this article I’ll show you how powerful it is and how to use it to create beautiful animations without too much complexity.
Let’s start with a basic example to show how we can move the red ball in interesting directions, such as the infinity shape:
Codepen t_aifi/eYvmOxR
As you can see, there’s no complicated code — just two keyframes and a ** “weird” ** cubic- Bezier () function. Instead, we create an infinite shape animation that looks very complicated.
That’s a pretty cool animation, right? Let’s dig deeper!
Cubic – the bezier () function
Start with the official definition:
A cubic Bezier easing function is a easing function determined by four real numbers. These real numbers specify the two control points of the cubic Bezier curve, P1P_1P1 and P2P_2P2, with endpoints P0P_0P0 and P3P_3P3 fixed at (0,0)(0, 0) and (1,1)(1, 1)(1,1), respectively. The XXX coordinates of P1P_1P1 and P2P_2P2 are limited to the range [0,1][0, 1].
The above curve defines how the output (yyy axis) will behave according to time (XXX axis). Each axis has a range of [0,1][0, 1][0,1] (or [0[0%, 100%][0). If we have an animation that lasts two seconds (2s2s2s) then:
If we want to animate from 5px to 20px on the left, then:
XXX, time, always limited to [0,1][0, 1][0,1]; However, the output Y can exceed [0,1][0,1][0,1].
My goal is to adjust P1P_1P1 and P2P_2P2 to create the following curves:
You might think this is impossible because, as stated in the definition, P0P_0P0 and P3P_3P3 are fixed at (0,0)(0, 0)(0,0) and (1,1)(1, 1)(1,1), meaning that they cannot be on the same axis. That’s true, but we’ll use some mathematical tricks to “approximate” them.
parabolic
Let’s start with the following definition: cubic-bezier(0,1.5,1,1.5), construct the following curve:
Our goal is to move (1,1)(1, 1)(1,1) and get it to (0,1)(0, 1)(0,1), which is technically impossible, so we’ll try to copy it.
We said earlier that our range is [0,1][0, 1][0,1] (or [0[0%, 100%][0), so let’s imagine that 00%0 is very close to 100100%100. For example, If we want to animate the top from 20px20px20px (00%0) to 20.1px20.1px20.1px (100100%100), then we can say that the initial and final states are equal.
Yeah, but our elements don’t move at all, right?
codepen t_afif/abJzoMq
Well, it will move a little bit, because the Y value is over 20.1px20.1px20.1px (100100%100), but this small change is not enough, or hard for us to perceive:
Let’s update the curve and use cubic-bezier(0,4,1,4) instead, notice that our curve is much higher than before:
Codepen t_afif/eYvmOoR |
However, there was still no change — even though the maximum exceeded 333 (or 300300%300). Let’s try cubic bezier(0,20,1,20) :
Codepen t_afif/VwpYZNV |
Yes! It starts to move a little bit. I don’t know if you notice how the curve changes every time we add value? When we zoom out to see the full curve, it makes our points (1,1)(1,1)(1,1) “visually” closer to (0,1)(0,1)(0,1), that’s the trick.
By using cubic -Bezier (0,V,1,V), where the VVV is a very large value and the initial and final states are very close (or almost equal), we can now simulate a parabola.
An example is worth a thousand words:
Codepen t_aifi/ZEeYzNy
I applied the “magic” cubic Bezier function to the top part of the animation and applied the linear function to the left, which gave us the curve we wanted.
Delve into mathematics
For those with a mathematical mind, we can break down this explanation further. We can actually define a cubic Bezier curve using the following formula:
Each point is defined as follows:
This provides us with two functions for XXX and YYy coordinates:
VVV is our high value and TTT is in the range of [0,1][0, 1]. If we consider the previous example, Y(t)Y(t)Y(t) will give us the value of top, and X(t)X(t)X(t) X(t) is the time progress. So now, the point (X (t), Y (t)) (X (t), Y (t)) (X (t), Y (t)) will define our curve.
Let’s find the maximum value of Y(t)Y(t)Y(t). For this, we need to find the value of TTT such that Y ‘(t)=0Y’ (t)=0Y ‘(t)=0 (such that the derivative of YYY function is equal to 000) :
Y ‘(t)=0Y’ (t)=0Y ‘(t)=0 is a quadratic equation. I’ll skip the boring part and give you the arithmetic result, which is:
When VVV is a large value, TTT will equal 0.50.50.5. Thus, Y = MaxY (0.5) (0.5) = MaxY = Max (0.5), (0.5) (0.5) X X X (0.5) will be equal to the 0.50.50.5. This means we hit the maximum in the middle of the animation, which fits the parabola we want.
In addition, Y(0.5)Y(0.5)Y(0.5) will give us 1+6V8\ Frac {1+6V}{8}81+6V, which will enable us to find the maximum based on VVV. Since we always use large VVV values, we can simplify to 6V8=0.75V\ FRAc {6V}{8}= 0.75v86V =0.75V.
We used V=500V=500V=500 in the last example, so the maximum there would be 365365365 (or 3750037500%37500), and we get the following result:
- Initial state (
) :top: 200px
- Final state (
) :Top: 199.5 px
The difference between 000 and 111 is −0.5-0.5-0.5px. We call that delta. For 375375375 (or 3750037500%37500), we have the equation 375∗−0.5px=−187.5px375∗−0.5px=−187.5px375∗−0.5px=−187.5px. Our animation elements reached top: 12.5px (200px−187.5px200px−187.5px200px−187.5px) and gave us the following animation:
Top: 200px (0% time) → TOP: 12.5px (50% time) → Top: 199.5px (100% time)Copy the code
Or, to put it another way:
Top: 200px (0%) → Top: 12.5px (50%) → Top: 200px (100%)Copy the code
Let’s do the opposite logic. What value of VVV should we use to make our element reach top: 0px? The animation will be 200px → 0px → 199.5px, so we need −200px−200px−200px to reach 0px0px0px. Our increments are always equal to −0.5px−0.5px−0.5px. The maximum value will be 20000.5=400\frac{2000}{0.5}=4000.52000=400, so 0.75V=4000.75V=4000.75V=400, which means V=533.33V=533.33V=533.33 V=533.33.
Our element touched the top of the container!
Here’s an illustration summarizing the math we just did:
Sine curve
We will use almost exactly the same technique to create sinusoids, but with a different formula. This time we will use cubic- Bezier (0.5,V,0.5, -v).
As we did before, let’s see how the curve evolves as we add value:
I think you can see by now that using a larger VVV gets us closer to the sine curve.
This is another animation with continuous animation – true sine animation!
mathematics
Let’s calculate this! Following the same formula as before, we will get the following function:
This time we need to find the minimum and maximum of Y(t), Y(t), Y(t). Y ‘(t)=0Y’ (t)=0Y ‘(t)=0 will give us two solutions. Solve the following equation for YYY:
… We get:
For VVV big value, we have t ‘= 0.211 t’ = ‘t’ = 0.211 and 0.211 t ‘= 0.789 t’ ‘ ‘ ‘= 0.789 t = 0.789. This means that Y(0.211)=MaxY(0.211)=MaxY(0.211)=Max and Y(0.789)=MinY(0.789)=MinY(0.789)=Min. This also means that X (0.211) = 0.26 X (0.211) = 0.26 (0.211) = 0.26 X and X (0.789) = 0.74 X (0.789) = 0.74 X (0.789) = 0.74. In other words, we hit the maximum 2626% of the time and the minimum 7474% of the time.
Y(0.211)Y(0.211)Y(0.211) is 0.289V0.289V0.289V, and Y(0.789)Y(0.789)Y is 0.289V, 0.289V, and 0.289V. Considering the VVV is very large, we got some rounded values.
Our sinusoidal curve should also cross the XXX axis (or Y(t)=0Y(t)=0Y(t)=0) half the time (or X(t)=0.5X(t)=0.5X(t)=0.5). To prove this point, we use the Y (t) Y (t) Y (t) of the second derivative, it should be equal to 000 – so Y ‘ ‘(t) = 0, Y’ ‘(t) = 0, Y’ ‘(t) = 0.
The solution is 3V6V+1\ FRAc {3V}{6V +1}6V+13V, and for large VVV values, the solution is 0.50.50.5. This gives us Y (0.5) = Y (0.5) = Y (0.5) and X (0.5) = 0.5 X (0.5) = 0.5 X (0.5) = 0.5, this confirms our curve through the (0.5, 0) (0.5, 0), (0.5, 0).
Now let’s consider the previous example and try to find the VVV value that brings us back to top: 0%. We have:
- Initial state (
) :top: 50%
- Final state (
) :Top: 49.9%
- Incremental: – 0.1-0.1-0.1%
We need −50−50%−50 to reach top: 0%, so 0.289V∗−0.10.289V∗−0.1%=−50%0.289V∗−0.1 gives us V=1730.10V=1730.10V=1730.10.
codepen t_afif/KKWwKpa
As you can see, our element touches the top of the container and disappears at the bottom of the container for a while, and so forth, because we have the following animation:
Top :50% → Top: 0% → Top :50% → Top: 100% → Top :50% → etc...
A graph summarizes the calculation:
And an example to illustrate all the curves:
codepen t_afif/RwpNwWz
Yes, you see four curves! If you watch carefully, you will notice that I used two different animations, one 49.949.9%49.9 (−0.01−0.01%−0.01 increments) and the other 50.150.1%50.1 (+0.01+0.01%+0.01 increments). By changing the sign of the increment, we can control the direction of the curve. We can also control the other parameters of the three Bezier curves (not the VVV parameters that should remain large) to create more variation from the same curve.
Codepen t_afif/qBrEBbJ
Here’s an interactive demo:
Codepen t_afif/OJpPJNV
Let’s go back to our example
Let’s go back to the original example where the ball moves around in the shape of an infinite symbol. I simply combined two sinusoidal animations to make it work.
If we combine what we’ve done before with the concept of multiple animations, we can get amazing results. Here’s the initial example again, this time interactive. Change the value in Codepen below and wait for magic ~
Codepen t_afif/rNyaNMJ
Let’s go one step further and add some CSS Houdini to the mix. Thanks to @Property, we can animate complex transformation declarations (CSS Houdini is currently only supported by Chrome and Edge).
Codepen t_afif/MWpYWbO
What shapes can you draw with it? Here are a few I was able to make:
! [CSS Alien drawing] (https://res.cloudinary.com/practicaldev/image/fetch/s – Xi6-3 ldi/c_limit % 2 cf_auto % 2 cfl_progressive cw_ % 2 cq_auto % 2 880/https://i0.wp .com/css-tricks.com/wp-content/uploads/2021/05/cubic-bezier-spirographs.jpg%3Fresize%3D1000%252C550%26ssl%3D1)
Here is a spiral animation:
Codepen t_afif/RwpNwKb
There is also a version without CSS Houdini:
Codepen t_afif/GRWgRWg
A few things can be seen from these examples:
- Each keyframe is defined with only one declaration containing a delta.
- The position of an element is independent of the animation. We can easily place elements anywhere without having to adjust the animation.
- We didn’t do any calculations. There are not a lot of angles or pixel values. We just need a small sum in the keyframe
cubic-bezier()
A large value in a function. - Simply adjust the duration value to control the entire animation.
The transition?
The same technique can be used for the CSS Transition property, because it follows the same logic with regard to the timing function. This was great because we were able to avoid keyframes while creating some complex hover effects.
This is what I did without keyframes. If you follow me, you’ll remember them as my collection of underline/overlay animations 😉
Codepen t_afif/mdWydmd
Mario is jumping because of the parabola. We don’t need keyframes at all to create a hover shake animation. Sinusoidal curves do all the work.
This is another version of Mario, this time using CSS Houdini. And, yes, he’s still jumping because of the parabola:
Codepen t_afif/abJzbWR
For a better measure, there are more fancy hover effects without keyframes (again, Chrome and Edge only). My next series reveal plot 😜
Codepen t_afif/poevowW
That’s it!
Now you have some magical Cubic – Bezier () curves and understand the math behind them. The benefit, of course, is that custom timing features like this allow us to create beautiful animations without the complex keyframes we normally need.
I know not everyone has a mathematical mind, and that’s okay. There are tools that can help, such as Matthew Lein’s Ceaser, which allows us to drag curve points to obtain the desired content. And, if we haven’t already bookmarked it, cubic-bezier.com is another option. If you want to play bezier curves three times outside of the CSS world, I recommend using Desmos, where you can see some mathematical formulas.
Regardless of how you got your Cubic – Bezier () functions, hopefully now you understand their power and how they help you write better code in the process.
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.