Performance Lessons from Writing a Book About Ionic Animations

I’ve recently finished writing a book about creating high performance animations and interactions with Ionic, which I put together over the course of around 3 months and ended up producing a chunky tome weighing in at about 500 pages. You can find out more about the book below:

Check out the book: Advanced Animations & Interactions with Ionic

Going into writing this book I felt that I had a good understanding of the issues facing web animations in general and how to implement them successfully in an Ionic application, but one of the great benefits of writing an in-depth book like this is that you tend to solidify and expand your knowledge of the topic a great deal. The purpose of this article is to highlight some key lessons about animations and performance that stood out to me by the time I finished writing that last page.

There are definitely animations and interactions out there that can’t be executed as smoothly within a web view as they could outside of the web view in a native application. I didn’t want to shy away from challenging scenarios for this book, so when finding examples to implement I picked a bunch of common and interesting scenarios without any regard to how feasible it would be to implement them with high performance using Ionic Animations and Gestures. Somewhat to my surprise, there wasn’t a single example I attempted that I ended up having to scrap from the book due to just not being achievable with smooth performance.

I’m sure these examples could be found, there is definitely a distinction between what can be done on the web and natively with animations. My point is that most likely the majority of animations and interactions that you would want to add to your applications can be done with a high level of performance in an Ionic application. As long as you have a decent understanding of how the browser renders frames to the screen, and how the animations/interactions you are creating interact with that process.

This article won’t be a rundown of general performance concepts, it will focus on things that I either learned whilst writing this book or things that became clearer for me through writing this book (e.g. that the limitations web views impose on animations aren’t actually all that limiting). Of course, I would recommend my new book to those Ionic developers of you wanting to gain a clearer understanding of performance and animations, but the key performance concepts to understand above all else are:

  1. Understanding the browser rendering process
  2. Achieving an average frame rate of around 60fps
  3. Keeping response times for interactions under 80-100ms
  4. Only animating transform and opacity (with rare exceptions for other properties)

1. You can do a lot with just transforms and opacity

It might seem extremely limiting at first to have a rule like “only animate transform and opacity”. When I set out to write this book I already had the opinion that there is a surprising amount you can do with just these two properties, but even still I expected to have to make some exceptions here and there. However, just about every single example in the book is achieved primarily with the transform property, this includes:

  • Sliding Drawers
  • Expanding/Shrinking Cards
  • “Star Wipe” style screen transitions
  • Draggable chat bubbles
  • Animated progress bars
  • Sliding action items
  • Tinder style swipeable cards
  • “Morphing” elements from one size/position to another
  • Parallax style shrinking/fading effects
  • Shrinking delete animations

Even an example where it seemed like there was no other way to achieve the animation without animating height was achieved through a transform (we will talk about that later).

2. The will-change CSS Property is Powerful

Going into writing this book, I didn’t yet have a full appreciation or understanding of the will-change property. It was something that had come up for me before, but I mostly considered it to be a kind of niche optimisation that has rare use cases and probably wouldn’t make all that much difference.

It wasn’t until I read Everything You Need to Know About the CSS will-change Property by Sara Soueidan that I gained a full appreciation for the will-change property and all of its potential (for both good and harm).

I won’t rehash here what is better explained in that article, but in general, the will-change property allows us to hint to the browser what we will be changing (e.g. a transform or opacity) such that the browser can then make the appropriate performance optimisations in advance (before our animation/gesture begins). This can lead to huge performance gains, but it is important not to overuse it, as telling the browser to optimise the wrong things at the wrong time can lead to performance consequences. The general approach I used in the book was to not use it at all, unless I could see that the performance of one of my animations/interactions was suffering in a way that might benefit from will-change. I would then implement will-change and measure the performance impact that it had (only keeping the change if it leads to performance improvements).

Quite a few of the examples in the book have had rather drastic performance improvements by utilising will-change. In some cases, this was the difference between sub-par performance and a consistent 60fps.

To give you an example, one of the components from the book involves sliding an <ion-item> to the right with a gesture (similar to Ionic’s <ion-item-sliding> component). Even though this gesture was achieved by animating a transform it was still causing constant paints throughout the gesture (we will talk more about paints in just a moment). The performance was still decent, but I was able to achieve a significant performance boost by utilising the will-change property.

How you use the will-change property is also important. Generally, it should only be added when you are about to use it and removed afterward. In this example, I utilised the onWillStart handler that is available through Ionic Gestures to dynamically add the will-change property to the specific element being dragged:

onWillStart: async () => {
  style.willChange = "transform";
},

and then removed it in the gestures onEnd handler:

onEnd: () => {
  style.willChange = "unset";
},

In this way, the browser is only optimising the element that is currently being interacted with when it needs it, not every single element on the screen that this gesture is attached to. If you have 50 of these sliding items on the screen, most you will never even touch, so performance optimisations on these would be a waste of browser resources.

On the other hand, for elements that will be constantly interacted with it is justifiable to add the will-change property directly to its CSS styles. I took this approach for the “sliding drawer” example in the book since it would likely be constantly opened and closed:

:host {
  will-change: transform;
}

3. The Power of Paint Flashing

I did a lot of animation optimisation throughout writing this book, so I was able to really hone in on what specifically in the performance/animation debugging toolkit was most useful. The Paint Flashing option that you can find in the Rendering drawer in Chrome DevTools was something that I seldom used, but now I have it enabled pretty much every time I am trying to optimise something:

Performance and Rendering Drawer in Chrome DevTools

What this option will do is flash a green box over areas of the screen that are triggering the “paint” step of the browser rendering process (i.e. new pixels are being rendered to the screen). If you can see a lot of paints being triggered throughout your animation/interaction it might be cause for concern - although a “paint” usually isn’t as concerning as triggering a “layout” in the browser rendering process, it can still have a significant impact on performance.

Sometimes paints are unavoidable, but sometimes its a telltale sign that an element that should be on its own layer and being, is not on its own layer. When rendering your application the browser will promote some areas onto their own layer that is “composited” into the rest of the application - this allows the layer to move around freely and be transformed without needing to repaint it all the time. However, the browser won’t always optimise your applications rendering perfectly, and being able to see where this is happening is immensely powerful. If we can spot things that aren’t on their own layer that we think should be, we can usually change that by using a transform or the will-change property.

Paint flashing enabled on swipeable tinder card component in Ionic

The example above is from one of the examples in the book before it was optimised. As you can see, as the gesture begins/ends it is triggering paint flashes. To optimise this, I was able to use the will-change property which completely eliminated these paints.

The Layer Borders option also helps with this, since elements that are on their own layer will be outlined, but I find this less obvious and don’t use it as much.

4. Testing with 6x CPU Slowdown

This is another one of those features of Chrome DevTools that I never really utilised, but that proved to be immensely useful. When creating a performance snapshop in Chrome DevTools, if you click the little cog icon in the top-right, you will be given some extra settings which include the ability to throttle the CPU performance.

Performance and Rendering Drawer in Chrome DevTools

The CPU is primarily what is responsible for doing the work required to render frames to the screen, so if we intentionally slow that down it can highlight potential problem areas in our animations/interactions more clearly. On a powerful machine, even some poorly designed animations can be executed well, but if we set the throttling option to 6x CPU Slowdown potential performance problems become more apparent:

Performance snapshot with 6x CPU Slowdown enabled in Chrome DevTools

I utilised this option for optimising every single example in the book.

5. The difference between jank and 60fps can be tiny

There are a lot of different ways you can go about implementing the same animations. There are the performance improvements that will quickly become obvious like using translateX(10px) to move an element 10px to the right instead of using margin-left: 10px (the former might only trigger the “composite” step in the browser rendering process, whereas the latter would also trigger “layouts” and “paints”). Then there are the more subtle and less obvious performance improvements like adding will-change: transform.

As we have touched on in this article, even with using good practices like only using transforms there can still be jank. Sometimes the kind where you can’t really see it but you can feel it, and that has a lot to do with not quite hitting a consistent enough 60fps. Really pushing for those little extra optimisations and paying attention to details by measuring performance can be the difference between an animation/interaction that just feels a little off, and one that feels smooth and satisfying.

6. Performance optimisations can get very creative

Sometimes squeezing out those last bits of performance can mean tiny details like will-change, sometimes it can mean taking a completely different approach to how your component works - perhaps even in ways that make it way more complex.

As you get more comfortable with performance concepts, specifically with the way the browser renders frames to the screen, you can start getting a lot more creative with your solutions and perhaps even achieve 60fps on animations/interactions that didn’t even seem at all possible. If you really understand what the browser is doing, it becomes a lot easier to see how to work with the browser rather than just telling it what to do.

There is a great example of this in the book where we optimise an animation that shrinks an item away when it is deleted by using height (which is bad for performance), and instead we use a transform instead to achieve the same effect. This sounds way simpler than it actually is, because there are reasons why using a transform in this circumstance is problematic. I created a summary of the approach for this in the video below:

These sorts of solutions can be very situation dependent, so the optimisation approach will differ vastly depending on what exactly you are trying to achieve. This is why I see this as kind of the “endgame” for animation/interaction optimisation on the web, it’s where you can really put your skills to the test. These are non-obvious optimisations that can make an otherwise seemingly undoable animation doable.

Summary

There were plenty more things I learned throughout writing this book, but these are the things that stood out to me the most related to performance. There is of course plenty more for you to learn as well if you are interested in check out out the book.

Advanced Animations & Interactions with Ionic

NEW Create next level Ionic applications by harnessing the power of the Ionic Animations API and Gestures.

Utilise these powerful APIs to design and build your own custom animations and interactions. No external libraries required, everything is built with just Ionic. Learn more.

Follow me on Twitter or subscribe to me on YouTube for more web development content.

Check out my latest videos: