TL;DR: iOS throttles
requestAnimationFrameto 30fps in cross-origin iframes and low power mode.
Yesterday I was looking at the examples on the Popmotion homepage with my iPhone. They looked odd, clearly suffering a sluggish stutter, the cruel affliction of (gasp) 30 frames per second.
Here’s an artificially-throttled example:
I’d seen this illness once before. If you’re looking at the example above on an iOS browser, check out this unthrottled example:
Looks the same, right? To other readers, this second example runs at a silky 60fps. Animation as the gods intended.
I found that it was only affecting animations running on CodePen. The reason was initially unclear.
I assumed that CodePen itself was doing something weird in the background that caused the stuttering. They helpfully pointed me to the Debug View, which runs the pen without the iframe.
It worked. 60fps on iOS. It wasn’t CodePen after all.
My first discovery:
requestAnimationFrame in iframes
Specifically, it turns out, cross-origin iframes. Which is every CodePen embed, even the ones hosted on CodePen’s own site (as the live panel is served on a subdomain).
As detailed in this WebKit changelog, the throttling is cleared once a user interacts with the iframe.
Try it for yourself. If you’re on iOS, take a look at the tween animation again:
This time, tap anywhere within the frame and watch as the 30fps eye-thrashing is replaced by the soft caress of 60fps.
I haven’t found a stated reason for this behaviour. But that doesn’t stop semi-educated speculation.
Iframes are commonly used for advertising. Adverts are fairly liberal with your CPU cycles. So I imagine the throttling is an attempt to prevent adverts from eating your battery.
The performance cap is cleared once the user interacts with the iframe and indicates that the advert (or other embedded content) isn’t unwelcome.
An alternative solution might be to throttle iframes that lie outside the current viewport (maybe pause execution entirely), and unthrottle when they enter. This current solution feels heavy-handed.
The remaining mystery
Back to the present, this didn’t explain why the examples on the homepage were stuttering. They’re part of the page itself, no iframes.
As the myriad of known and unknown potential causes flashed before my eyes, a familiar feeling of nausea set in.
Standing on the precipice of a rabbit hole of unknown depth, I began the ritual.
Opened my MacBook. Chrome. Performance tab. Throttled CPU… 60fps.
Checked my partner’s phone, same OS, same make and model… 60fps.
Googled for bugs with iOS 11.2.1 and 30fps rAF… Nothing. What the fuuuuuu…
I stared at my phone, dumbfounded. Maybe I needed to reset my phone. Maybe it was because my phone was the wrong colour. Maybe I simply needed to be kinder to Siri.
Luckily, I saw it, and I knew I’d found the culprit. The nausea lifted. Relief settled:
Take me from this earth
Low power mode. Though part of me wouldn’t believe it until I toggled it on and off and saw the difference with my own eyes, it made sense.
My second discovery:
requestAnimationFrame in low power mode
As detailed in this WebKit bug, iOS throttles
requestAnimationFrame to 30fps whilst low power mode is active.
I have mixed feelings about this.
First, there’s no doubt that it’s very clever. It is, in the short-term, a boon for users.
In the long-term, I’m not so sure. I’ve already covered in detail why the mobile web is a dreadful platform to develop for.
This is yet another unpredictable tributary feeding into a much larger river of unpredictable nonsense that we have to put up with.
The salmon will get tired. It’s a self-perpetuating cycle. Horizons, ambitions and standards will drop. Further emboldening mobile browser vendors to push for clever solutions to improve user experience and degrade the developer one.
A clever, battery-saving shame.
If you still have any energy left in your tired little salmon fins, make the mobile web an app-quality kinda place with Popmotion. Alternatively, throw your opinions in my stupid salmon face on Twitter