Smooth transitions in Cordova / Knockout


15 January 2016, by

Here at Softwire, we’ve been busy building a mobile app for a chain of health clubs, and this is the third post in a four-part series about our experience and what we’ve learned. Watch this space for the next instalment, or follow us on twitter for updates!

We’ve chosen to build the app using Apache Cordova, so our code can easily run on both iOS and Android platforms. Take a look at the first post to read about what Cordova is or the second to see some of the other practices we use to make Cordova feel as app-like as possible.

One of the important requirements was for scrolling and transitions to be smooth and to avoid the screen juddering. Lack of polish and smoothness is one of the most common objections raised against Cordova; it’s an easy trap to fall into, but simpler than you might expect to solve once you’re aware of it.

Work with the phone

Modern phones have powerful graphics processors, so we want to offload as much of rendering onto the GPU as we can, to keep the CPU free and ensure our rendering goes at top speed.

Let’s say we’re using CSS to move an element ‘my-page’ off the right of the screen by applying the ‘off-right’ class.

We could write:

#my-page {
    left: 0;
    transition: left 1s;
}
#my-page.off-right {
    left: 100%;
}

This will work, but it’s not typically run on the GPU by most browsers, which means performance may take a hit if you have many or large elements moving like this across the page.

Fortunately we can replace this equivalent to get GPU acceleration, with:

#my-page {
    transition: transform 1s;
}
#my-page.off-right {
    transform: translateX(100%);
}

The ‘transform’ property here is designed to be easy to handle on the graphics processor, and so runs much more smoothly.

Don’t stop me now

We’re building our app as a single-page website, within Cordova. As part of this, we’re using Knockout.js to do data bindings. Knockout is a simple way of keeping your javascript object models in sync with the HTML (or ‘binding’ them together, in Knockout lexicon).

It’s important to understand how this works to improve performance though, with Knockout or other similar frameworks. When you change the value of a Knockout-observed variable, Knockout will immediately make the corresponding change to the HTML, and each of these changes in the HTML will cause the browser to re-render the relevant part of the page. If a large part of the page is moving when this re-render is trigger then it will appear to stop, re-render and then continue moving, causing a noticeable judder.

This is particularly noticeable if you update properties across an array of objects. Knockout interprets each of these as a separate change and causes the page to re-render multiple times, resulting in a very juddery transition! Doing this is generally bad for rendering performance, but it’s a very easy pattern to fall into with many modern single-page app frameworks.

To solve this, in our app we’ve been careful to make sure we never change knockout-bound values whilst we’re transitioning. When we move between pages, we’ve adopted a workflow like this:

  • 0 ms
    Create new page, start loading data and binding each part to the page.
  • 100 ms
    Pause data bindings. Put any data change requests in a queue.
    Start transition (transition lasts 500ms).
  • 600 ms
    Resume data binding. Process any queued requests.

In our testing we’ve found that the first 100ms is typically long enough for data to be fetched from the cache and rendered, when it’s already cached, without being long enough for the user experience to suffer.

Nothing to see here

Generally, we’ve been very happy with the performance of Knockout, but it’s performance can degrade with increased load. Every data-bound value causes extra work for it, and slightly reduces performance. For best performance, we should minimise the amount of data that Knockout is binding.

For us, this means segmenting the app into different ‘journeys’ and ‘pages’ and ensuring Knockout is only data-binding the journey and page currently in use, and disposing of the data and DOM elements not relevant to the current page. Within some of our larger pages, we take this further, and change what data is bound as the user moves around the page. This requires some discipline for us to remember to destroy pages and journeys as soon as we’ve finished viewing them, but has been well worth it in performance benefits. If there’s “nothing to see here” then it’s best if Knockout doesn’t have to keep track of it.

All this has come together for us fantastically; despite being built with Cordova the app feels snappy and quick across a wide range of devices, and our customer is very happy with the result.

In the fourth and final post we’ll look at how we architected the app to ensure maintainability for the future. Keep an eye on our blog for the next edition, or follow us on twitter for updates.

Categories: Mobile, Technical, UX / Design

«
»

Leave a Reply

* Mandatory fields


6 − two =

Submit Comment