Skip to main content

Moving Items Along Bezier Curves with CSS Animation (Part 1: Constructions)

TLDR: This article is NOT about cubic-bezier(), and it is not about layered CSS animations (for X and Y axes respectively). It is about carefully crafted combined animation, that moves an element along any quadratic/cubic Bezier curve.

UPDATE: Here's the link to part 2.

Following my previous post, I continued investigating competing CSS animations, which are two or more CSS animations affecting the same property.

I observed that two animations may "compete". In the following example, the box has two simple linear animations, move1 and move2. It turns out the box actually moves along a curved path:

So clearly it must be the combined effects from both animations. Note that in move2, the `from` keyframe was not specified. It'd look like this if it is specified:

In this case, it seems only the second animation takes effects.

Actually this is not surprising, in the first case, the starting point of move2 would be "the current location from move1".  But in the second case, move2 does not need any "current location", so move1 would take no effect.

I further examined the actual behavior of the first case. If move1 is "move from \(P_0\) to \(P_1\)" and move2 is "move to \(P_2\)". At time \(t\):

- The animated location of move1 is \(Q_1=(1-t)P_0 + tP_1\)

- The animated location of move1+move2 is \(Q_2=(1-t)Q_1 + tP_2\)

This formula actually looks very similar to the Bezier curve, but I just double checked from Wikipedia, they are not the same.

Fun fact: I came up with this string art pattern during high school, I realized that it is not a circle arc, but I didn't know what kind of curve it is. Now I understand that it is a Bezier curve.

Quadratic Beziers in string art


Build a quadratic Bezier animation path with two simple animations

The quadratic Bezier curve is defined by this formula:

\[B(t) = (1-t)^2P_0 + 2(1-t)tP_1 + t^2P_2, 0\le t \le 1\]

But our curve looks like \(f(t) = (1-t)^2 P_0 + t(1-t)P_1' + tP_2\). Note that I use \(P_1'\) to distinguish it from P1 above.

If we set \(P_1'=2P_1-P_2\), we'll see that f(t)=B(t) for all t. So it is a Bezier curve, just the control point is a bit different.

Here's an interactive demo, which is based on this codepen.


An Alternative Version

Another option is to follow the construction of a Bezier curve:

Bézier 2 big

This version needs slightly more code, but it does not require much math. Just observe that a quadratic Bezier curve is a linear interpolation of two moving points, which are in turn obtained by another two linear interpolations.

All these linear interpolation can be easily implemented with CSS animation on custom properties. Here is an example:

Here I just have one animation, which does multiple linear interpolation at the same time. In this case I have to make sure all animated custom properties are defined with @property, which was not the case in the previous example.

How about cubic Bezier curves?

Both versions can be extended to make animation along cubic Bezier paths.

The first version needs a bit more math, but doable. 

\[ P_1' = 3P_1 - 3P_2 + P_3\]
\[ P_2' = 3P_2 - 2P_3\]


The second version just involves more custom properties.


Actually both versions may be extended to even higher-degree Bezier curves, and 3D versions.

For the first version, I suppose there would be a generic formula for any \(P_i'\) for any \(N\)-order curve, but I did not spend time in it.

Comments

Popular posts from this blog

Determine Perspective Lines With Off-page Vanishing Point

In perspective drawing, a vanishing point represents a group of parallel lines, in other words, a direction. For any point on the paper, if we want a line towards the same direction (in the 3d space), we simply draw a line through it and the vanishing point. But sometimes the vanishing point is too far away, such that it is outside the paper/canvas. In this example, we have a point P and two perspective lines L1 and L2. The vanishing point VP is naturally the intersection of L1 and L2. The task is to draw a line through P and VP, without having VP on the paper. I am aware of a few traditional solutions: 1. Use extra pieces of paper such that we can extend L1 and L2 until we see VP. 2. Draw everything in a smaller scale, such that we can see both P and VP on the paper. Draw the line and scale everything back. 3. Draw a perspective grid using the Brewer Method. #1 and #2 might be quite practical. #3 may not guarantee a solution, unless we can measure distances/p...

Chasing an IO Phantom

My home server has been weird since months ago, it just becomes unresponsive occassionally. It is annoying but it happens only rarely, so normally I'd just wait or reboot it. But weeks ago I decided to get to the bottom of it. What's Wrong My system set up is: Root: SSD, LUKS + LVM + Ext4 Data: HDD, LUKS + ZFS 16GB RAM + 1GB swap Rootless dockerd The system may become unresponsive, when the IO on HDD  is persistantly high for a while. Also: Often kswapd0 has high CPU High IO on root fs (SSD) From dockerd and some containers RAM usage is high, swap usage is low It is very strange that IO on HDD can affect SSD. Note that when this happens, even stopping the IO on HDD does not always help. Usually restarting dockerd does not help, but rebooting helps. Investigation: Swap An obvious potential root cause is the swap. High CPU on kswapd0 usually means the free memory is low and the kernel is busy exchanging data between disk and swap. However, I tried the following steps, none of the...

Fix Google Security Code

Google Security Code (http://g.co/sc) is one type of 2-step verification. This is particularly useful when security keys and passkeys are not available. I have been using it in my LXC containers, until today I found out that it stopped working. It just kept saying "The code is invalid". It is easy to rule out some factors: The code works on other browsers on my laptop. The code works on other devices that are directly connected to the router. So it appears that Google also checks IP addresses besides the security code. Recently I have IPv6 enabled, so most devices that are directly connected to the router have both IPv4 and IPv6 addresses. But  I only enabled IPv4 for my LXC containers. So I guess when a code is generated by device A and used by device B, Google should be able to check that device A and device B are closely located. But in my case, IPv6 address appears on device A but not on device B, which may look suspicious. To fix the problem, I just needed to disable IPv...