60 FPS JavaScript swiping on mobile

A mistake that is worth 30 extra frames per second

Following my Cross-platform email client with nothing but HTML article, we’ve been told by many people that they’d love to hear more about some tricks that make our app feel so native and fast.

Take our conversation list for example and the steps we took to get a 60 FPS on (almost) every mobile device. For the simplicity of this article, we’ll just assume that we have a list ( ul ) of items ( li ). Also note that no transition or animation are involved when I’m talking about swiping; I’m just referring to the items following the touch as you move your finger.

Initial naive version

li are normally positioned. Upon swiping, each of the targeted li (hello Multi-Swipe™) has its transform: translateX() updated. Chances are that if you test on a very powerful and recent mobile or Chrome with device toolbar enabled, it’ll be as good as native. On my iPhone 6, we thought it felt pretty good for a JS swipe. Only then did we buy a Google Pixel to develop the Android version and realized how much more responsive the swiping was on that device. My phone not being that old, we knew there was some improvements that could be done.

1. translate3d

I didn’t plan on talking about this one and start the initial version above with translate3d , but truth is I did use translateX at first. It was my understanding that nowadays translateX is just as hardware-accelerated as its 3d equivalent. However it felt a little bit more fluid with translate3d , especially on mobile. 🙌🏼

2. Absolutely positioned items

An issue on my phone that we didn’t feel on the Pixel was that there would be some delay between the moment the touch move began and the first rendering of the swiped elements. The UI was frozen during the first 10px and then the items jumped to the position they were supposed to be. 🤢

We figured it was the hardware accelerating rocket booting up. 🚀

So we put the li in an already “accelerated” state by absolutely positioning them and tranlate3d to their position. It worked! No more delay on my phone at the beginning of the gesture. 🙌🏼

p.s. I know about the infamous backface-visibility: hidden trick, but in our case it didn’t seem to change anything.

3. The x-axis & the mistake 🙊

Now that the items are translated on the y-axis, all we need to do is translate the x when swiping while keeping the y and we’ll be done!

What I thought I was doing…

But here comes “the mistake”. I somehow mixed some React refs and ended up translating the underlying div instead.

What I did instead, by mistake.

We could instantly feel the difference and knew we had finally reached that native feeling that we were looking for. See the difference for yourself:

Pre-mistake:

Translating the same element on both axis

If you think this is video recording lag, wait until you see the next one.

Post-mistake: