I have been experimenting a lot with HTML5, JavaScript and especially CSS3 on a variety of mobile devices recently. I want the mobile applications that we build with web technologies to continue to provide all the cross platform and deployment benefits that our clients have been enjoying with our desktop based trading apps, but I want them to continue to look great, I want them to continue to provide the user with a fantastic experience and in order to do so they must perform well.
As Sencha has shown with their FastBook demo it is more than possible to make mobile web based applications that look and feel as good, if not better than their native counterparts, but it is fair to say that in order to do so you need a deep understanding of the languages, browsers and design patterns that you are using.
So although we have many years of experience building complex, real-time trading applications in the browser we never stop experimenting, especially with new mobile devices, and my more recent experiments have been with hardware accelerated CSS properties on mobile.
Hardward accelerated transitions : The classList problem
As detailed in Paul Lewis and Paul Irish’s post High Performance Animations, the four things a browser can animate cheaply are:
- transform (both 2d and 3d)
- scale
- rotate
- opacity
I have had a great time with these properties, by combining them you can create almost any transition that you will find in popular native applications and they perform very well.
But I noticed a surprising issue, especially when translating complex DOM trees which I didn’t expect as the transition should be handled by the GPU (and is). The transition was always silky smooth but with complex trees there was a long delay before the transition begun. The problem got worse with larger trees.
Taking a look at the chrome console the delay was being caused by an expensive Recalculate Style. This was being triggered because to initialise the transition I was adding a class to my container element.
element.classList.add("transition");
Even though the ‘transition’ class only contained a single property that shouldn’t really need to cause an expensive Recalculate Style it was happening anyway becuase I added a class.
Moving the property modification into JavaScript resolved the issue.
element.style.webkitTransform = "translate(500px, 0)";
The issue can be seen at the codepen I put together.
http://codepen.io/stevesouth/full/xcfGk
Note: There is a JavaScript variable treeDepth which unsurprisingly controls the size of the large DOM trees.
For the timeline below i had this set to 100000 and tested in Chrome. Firefox seemed to crash at anything above 20000, IE11 was OK. If you experience this issue you can load the pen with JS disabled by adding ?
turn_off_js=true to the url which gives you a chance to change the parameter. You shouldn’t need to go so high to see the difference in the Chrome timeline.
The following timeline screenshot shows the issue perfectly.
Clicking each of the four elements in a row, it is clear from the timeline that there is an expensive Recalculate Style happening when you click the third element. This is eliminated when the style modification is moved from a class to a JavaScript modification.
Other Browsers
Although this post has been heavily focussed on Chrome, I have ensured that the codepen works in the latest version of IE and Firefox. Safari should be fine too, however I haven’t done much investigation into how any of these browsers behave yet, please comment below if you find out.
In summary…
If you are experiencing delays with your transitions check the Chrome timeline, see if some unnecessary Recalculate Styles are firing and if so you might want to make a similar change. Most great animations libraries such as animate.css use classes, which makes perfect sense, but could lead to the issue above.
Saying all this, please don’t leave this post just thinking ‘Classes bad, JavaScript good’ for this use case. I agree with Paul Lewis that in general you should be thinking ‘tools not rules’ and I would recommend you take a look at his Guide to Rendering Performance presentation.
It may well be that browsers resolve this issue in the future, I am not sure why they couldn’t already know that adding the ‘transition’ class doesn’t require a Recalculate Style. So at some point my advice above will hopefully becomes completely useless. Unless that is, you start using the Chrome timeline a bit more.
this is such a great article!
wonder why no one has left a reply to it 🙁
Thanks Jake, glad you enjoyed it!
This is really eye-opening. I’m all in favor of using class changes, so I do hope the browser developers handle this issue. Glad we can at least use class changes on a shallow level without much trouble still.
Thank you for sharing this impressive (and needed) research!
thanks a lot, this is very useful to know for my React Phonegap application 🙂 I have some transition problems on pane swipes
Hi, great article.
I have a question. I have a large table with 10k rows and currently I am setting transformation to a specific class in the table. This is really slow. Do you maybe have a suggestion for this problem?
One solution is to render only the elements visible or let say some length of elements, that it still works ok, but if there is a simpler solution then that.
THX.
var headRows = jqElm.find(‘.BigTable-StickyCol’);
jqElm.find(‘.BigTable-Grid tbody’).on(‘scroll’, function (e) {
if (prevScrollTop !== e.target.scrollTop) {
prevScrollTop = e.target.scrollTop;
headRows.css({
transform: ‘translateY(‘ + (-prevScrollTop) + ‘px)’,
MozTransform: ‘translateY(‘ + (-prevScrollTop) + ‘px)’,
WebkitTransform: ‘translateY(‘ + (-prevScrollTop) + ‘px)’,
msTransform: ‘translateY(‘ + (-prevScrollTop) + ‘px)’
});
}
});
Thanks for your post, Stephen. I’ve been struggling to pin down a very serious performance issue on IOS, particularly painful on iPhone5/iPhone6. Similar to your case, I start a transition over a complex element using a classList addition. In particular, I slide out a nav menu over a listview of ~50 rows. Dog slow on iPhone5, better on iPhone6, OK on iPhone7 and not a problem on Android or desktop browsers. No additional profile activity during the transition.
Your article leads to an entirely new set of experiments. I tried to replace the classList addition with a direct assignment via JS, but it had no effect. Still see a large multi-second Style Recalculation before and after the 300ms smooth CSS transition. Unfortunately, Safari Web Inspector does not provide the diagnostic granularity to get more info on the cause of the recalculation. I’m unable to connect Chome DevTools as a front end to IOS, so I’m stuck fumbling in the dark. I’ve posted a stackOverflow question with a bit more detail, but have yet to get any replies to this insidious problem. It’s like being in a jail cell and not quite able to reach the keys on the floor to get out.
http://stackoverflow.com/questions/42477011/nav-menu-slider-over-listview-performs-badly
Any ideas would would be most welcome. Thanks again for inspiring some new veins of thought on this. Maybe there is hope!