Speed-up UI with separation of rendering and state

Aurelijus Banelis

Software developer and author of Auginte project.
Technology enthusiast and a speaker, sharing knowledge in local technology meetups (E.g. VilniusPHP, NFQ talks) and conferences (E.g. Infoshow, Fosdem).
When using touch events with JavaScript on mobile web – slow response is visible. Solution: Update state, but render HTML only by constant timer.

Why mobile web?

Wanted real time updatesFirst version of Auginte zooming based organizer was designed for Desktops, because I was the main (and usually the only) user. But today, if information sharing is the goal – the most common platform remains mobile phone / tablet.

Instead of creating specialized applications for Android, iPhone, Windows Phone and other platforms – I decided to firstly create mobile web version. So basic functionality could be always accessible for majority of the users. Moreover, popularity of “getting mobile app for every aspect of your life” is also decreasing – at least due to higher and higher battery consumption and privacy concerns.

Problem: Slow single threaded environment

When using Desktop application or even Web browser of Desktop computer – you do not feel any lag, because all responses to input are rendered immediately. Rendering time became a problem for me only with large amounts (E.g. 1000) of objects.

For a long time, I resisted switching to mobile web, because there were already memory and speed issues using Desktop version with large amount of elements (or big pictures). How could it still be usable on limited hardware mobile devices?..

First web based Auginte tool: Touch based interaction is hardDespite the fact, new mobile phones have processing power of not old Desktop computer – keeping all interactivity of moving and zooming for me was still a challenge. Therefor, in first web version of Auginte zooming based organizer only touch start and touch end events were updating the state. There were no update while dragging with your finger, because old touch events were stacked and rendered even seconds after dragging was finished. By the way – you can try it online. Same and even bigger problem was with zooming: distinguishing pinch zoom, rotating and moving with 2 fingers was really error prone, especially when user was not getting immediate feedback.

Solution: skip old updates – keep only newest


In the video you can see updating HTML immediately (left side) versus separated state-HTML update (right side). Main code change was:

ElementsApp.subscribe(ElementsApp.zoom(identity))(_ => render(root))
ElementsApp.subscribe(ElementsApp.zoom(identity))(_ => needUpdate = true)
Async.timer(1000 / updatesPerSecond) {
 if (needUpdate) {
   needUpdate = false
   render(root)
  }
}

In code example, instead of updating HTML immediately (render(root)), variable is used to mark the need for update (needUpdate = true) , so actual HTML rendering is triggered via timer (Async.timer is a wrapper for setInterval). So there are less updates, because only latest state is being used for HTML update.

Example is taken from this commit, so you can reproduce situation in the video using revisions before and after the change. Also, you can try draft version online and compare interactivity with older Web based Auginte tool implementation.

Alternatives: Virtual DOM?

Probably examples with Scala.js + Diode are not so common (therefore not easy to understand), but immediate-state versus timer-update principle is independent from framework or programming language. Mobile web example is easiest to demonstrate, but it is not the first even in Auginte project, not to say about game development industry.

Going back to WEB industry, Virtual DOM is closest to principle being discussed:

When a component’s props or state change, React decides whether an actual DOM update is necessary by comparing the newly returned element with the previously rendered one. When they are not equal, React will update the DOM.

As stated in quotation, React.js is used more to check what to change, not when to change. To make your React.js applications faster, you could implement:

shouldComponentUpdate(nextProps, nextState) {
  // Code to compare with frames per seconds
}

At least for first web based Auginte tool, React.js was not the best solution, because it was solving different problem: when most is the same, only small is changed. In Auginte project: by moving or zooming camera almost all (virtual DOM) elements were updated, so timer based approach showed to be better solution.

Debugging and testing mobile performance

Performance issues are best to test on real devices, unless you have time for sophisticated profiling infrastructure. For example, using Chrome DevTools touch events seemed to work very fast, but on actual mobile device it was lagging.

render vs state: debuggingI could connect Desktop browser with Mobile browser, but in current stage adding 2 elements to show metrics for Frames per second and HTML refresh count was sufficient.

So I saw, how Frames per seconds (fps) were dropping with more touch events being stacked. After optimization, I saw that loading of all old elements from storage took only 1 HTML update – so code was working.

Conclusion

Now I am writing this post, and it seems so obvious – still I spent many iteration to came up with this solution. Simple, but powerful one 🙂