Programming

# Yummy UI food with keyframe animations

When I saw the below demo I thought “neat, how’d they do that”? That may sound surprising if you knew that I wrote the API for this. In this article I’ll describe how three orthogonal features, generic navigation, event triggers and keyframe tracks come together to produce this pleasing animation.

### Keyframe animation

Creating the jagged path is perhaps the easiest part. Keyframe animation is something I added to the API a long time ago — our users felt understandably constrained with only simple easings.

Let’s take a look at one-half of the animation, the movement to the left of center.

 ```1 2 3 4 5 6``` ``` ```

Our animation engine is built around moving things away from where they normally are. In this case, each card has a position on the screen determined by the layout engine. This `Move` says how to move it away from that position. The `RelativeTo="Size"` also uses the layout size: the fractional values for `X` and `Y` are multiples of the element’s size. You can visualize the path in your head, or just look at the animation again.

#### Spline track

A jagged path works well in this example, but it’s not always desired. We could instead put `<Move KeyframeInterpolation="Smooth">` to create a path with rounded corners — it creates a spline curve from the values.

The animators uses dynamic composition to implement the path. If a `Keyframe` is used it uses the `SplineTrack` provider, otherwise the `EasingTrack` provider is used. There are a couple more for dealing with discrete values.

How does it create the smooth curve? In short, I’m using the Kochanek-Bartels equation to get tangents, which are turned into a cubic Hermite spline. I ended up using the exact same approach (same code even) to create a smooth curve in our vector drawing. You can read more about this in my spline article.

`Move` specifies the complete path, but the cards are moving only part of the distance on each transition. This is where the linear navigation comes in.

The tricky part in Fuse was to define a navigation system that didn’t have prebaked animations. We wanted to give designers free reign in what navigation looks like on the screen. Getting this “right” took a lot of iterations in the code. This history is still apparent in the interfaces for navigation — I have an outstanding issue to clean this up a bit. Okay, but how does it work?

Each card is a page in a `LinearNavigation`, giving them a specific order in the navigation (you can see the left-right order in the demo). The active page is given progress 0 for navigation. The page just “in front” of it is given progress 1, the page just “behind” it has -1. All pages are given a progress based on their distance to the active page. The page two behind the active one has -2, three behind, -3, etc.

These values are continuous. When the navigation switches pages it gradually alters the values; for example, from 0 to 1 for a page moving forward. In this particular example the navigation is configured to use a “CircularOut” easing for the transition.

The key point here is that the navigation only deals with these progress values. It’s blissfully unaware of the visual representation of the pages. How then is the animation achieved?

#### Scaled animators

The `Move` I showed above is set inside an `EnteringAnimation` trigger.

 ```1 2``` ``` ```

`EnteringAnimation` subscribes to progress update events on the page (this uses a C#/Uno `event`). It converts the page progress value into a progress value for the `Move` timeline — it seeks to this new value. By default it maps progress 0 to 0%, and progress 1 to 100%.

The `Keyframe` items specify a `Time` parameter, which is given in seconds. When driven by something like `EnteringAnimation` the actual seconds part is ignored — the timeline is normalized from 0% to 100% progress and driven by the progress value of the page.

We don’t want to animate over the entire `Move` timeline. We only want to navigate a bit of the way. This is what the `Scale="0.25"` does. The page progress value is multiplied by this amount. A page with progress 1 will thus only seek to 25% of the animation. A page at progress 2, two away from the active one, will seek to 50% in the timeline. These scaled values match the `Time` values given in the `Keyframe`.

The counterpart to `EnteringAnimation` is `ExitingAnimation`. We saw that the page progress values can be positive or negative. `EnteringAnimation` deals only with position values. `ExitingAnimation` deals with the negative values — converting -1 to 100%. This is how we can provide different animations for pages “in front of” and “behind” the active page.

The names “Entering” and “Exiting” were chosen based on early use-cases where pages were visually entering and leaving the screen. This was actually done prior to coming up the page progress mechanism. We’ve debated often about these names, yet never managed to come up with something clearer. Nobody seemed to like my `PositivePageProgressAnimation` suggestion.

### Other triggers

This demo is a good example of the value in an orthogonal API:

• The navigation system cares only about modifying a page progress value. Whether it’s a timed transition, or the user swiping on the screen, it’s all mapped back to this simple progress value.
• The trigger system, like `EnteringAnimation`, cares only about converting this value into an animation progress. This makes it easy to create other triggers, like `ExitingAnimation`, or `ActivatingAnimation` and `DeactivatingAnimation` if the direction isn’t relevant. It also allows for `WhileActive` or `WhileInactive`, which can both specify a `Treshhold` for how active they need to be.
• The `Move` timeline is just a generic animator and doesn’t care what’s driving it. A `Rotate` or `Scale` could be plugged in here just as easily. The `Keyframe` applies equally to any of these animators.

It’s nice to see it work out so well in this demo.