Force correct CSS3 transform percentage interpretation on Android

Spread the love

Question Description

tl;dr? Get the mechanism demonstrated in the link below to behave with GPU acceleration on Android Chrome & default browser.

UPDATE 2 (2014-01-13 13:25:30Z): According to bref.it‘s comment below, the reported behaviour is fixed as of Android 4.4 KitKat — but the fix I described below now breaks it! Sod’s law.

UPDATE 1 (2012-11-01 17:54:09Z): The buggy behaviour is inferrable from the transformation matrix as reported by the computed style of the transformed element, which returns a pixel value. I’ll try to write a Modernizr test for this to pave the way for any possible solutions.

I’ve developed a mechanism for sliding a container to reveal full-width, horizontally arranged sub-sections. Sliding tabs, basically. Because there’s a lot of performance intensive stuff and elaborate Javascript going on, I want to keep JS to the minimum and do as much of the purely stylistic in CSS. I think I’ve done pretty well, considering — JS just changes an attribute on the wrapper:

(w/ left)

http://jsfiddle.net/barney/VPJuq/
(mobile devices can append /show/ to these fiddle URLs to see the results by themselves)

A word on how this works: treating the tabs as inline-blocks allows me to specify white-space: nowrap (the rest of the code in the last couple of rules essentially collapses the whitespace between the tabs) and allow them to stack horizontally without clearing / returning, all the while each maintaining the full width of their parent. From there, setting a negative left offset to the wrapper does the magic. Cool, huh?

Now, a bit of context: the interface I’m developing is to be run in native mobile applications — the application’s core functionality relies on cutting edge mobile-specific technology (don’t ask — NDA) — via a UIWebView, and the only platform that currently supports the technology in question is Android.

Here my problem is twofold: transform: translate works a /lot/ smoother (translate3d even more so) than left or margin-left transitions, to a point that is really really desirable, borderline crucial — especially on Android, seeing as non-translated transitions are still stutteringly glacial on the latest phones with the latest OS. With that in mind, the crux of the issue is that Android seems to infer the box model differently when relating to translate. To demonstrate, here’s a transform-based version of the same thing, which does the same thing as the previous fiddle, and works on all browsers that support translate3d…

(w/ translate)

http://jsfiddle.net/barney/EJ7ve

If you examine this on iPhone (again, by appending /show), you’ll notice improved frame rate on iPhones. The same goes for Firefox running on Android, and arguably on Chrome and the default browser on Android too, except that here, the translateX offset of -100% somehow refers to the space occupied by all three tabs, so the wrapper slides just enough such that none of the tabs are visible. This is odd, since transform percentages are specified as relating to the full box model of the element being transformed — and computed style unambiguously describes the wrapper width as being the same as its parent (not, as the result implies, stretched 3-fold to accomodate the tabs).

Can we describe this as a bug?

As far as I can tell, there’s no pure-CSS (I’m not averse to media query feature detection, CSS vendor forking or property hacking) method of infering and resolving this problem. In fact the only way of making this same CSS mechanic work that I can think of now is sniffing the UA string for Android and applying different rules conditionally. Yuk! If I’m to make an Android-WebKit-only solution, and break functionality everywhere else, I can take the de facto behaviour in to account and make the percentages refer to fractions:

(w/ translate — for Android update: unnecessary and breaks on Android 4.4 KitKat)

http://jsfiddle.net/barney/nFt5t/

Not ideal, as this makes the UI browser-specific and requires that we know up front the number of tabs in a group before writing our CSS. But this doesn’t even completely solve the problem there, as, on orientation change (and this is really weird), the offset switches to the specification-correct behaviour displayed by other browsers!

I’ve also tried applying the translation to the actual tabs, which again works everywhere else, but despite correctly picking up and applying the rules, Android’s browser won’t render the effect. Stranger and strangerer:

(w/ translate, working on Android’s theory, not working on Android at all)

http://jsfiddle.net/barney/EJ7ve/9

Any insights or ideas for a cross-browser solution much appreciated.

Practice As Follows

I am using Android 2.3 and can’t produce the exact problem you describe (with regards to your comment “-100% somehow refers to the space occupied by all three tabs”). However, I was experiencing other issues resulting in intermittent/jumpy transitions and some other weirdness I didn’t really investigate.

For the sake of conversation, lets assume that the “wrapper” is 500px wide.

By using your inline-block/nowrap technique (which is a very under-appreciated layout technique from YUI), your 3 tab panels are taking up 1500px of horizontal space. Based on what you describe, you need to spoof the browser into thinking all of tab panels together are taking up 500px of horizontal space. Checkout my fiddle, which essentially uses some negative margins and translates to trick the browser in a way I think should work, but I can’t test it. Either way, my example fixed the weirdness I was experiencing on my Android 2.3 – so you may want to use it either way.

http://jsfiddle.net/ryanwheale/EJ7ve/29/

(and the relevant CSS)

.tabs > * {
    width: 100%;
    margin-left: -100%;
}
.tabs > *:first-child {
    -webkit-transform: translate3d(0%, 0, 0);
    -moz-transform: translate3d(0%, 0, 0);
    -ms-transform: translate3d(0%, 0, 0);
    -o-transform: translate3d(0%, 0, 0);
    transform: translate3d(0%, 0, 0);
}
.tabs > *:first-child + * {
    -webkit-transform: translate3d(100%, 0, 0);
    -moz-transform: translate3d(100%, 0, 0);
    -ms-transform: translate3d(100%, 0, 0);
    -o-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
}
.tabs > *:first-child + * + * {
    -webkit-transform: translate3d(200%, 0, 0);
    -moz-transform: translate3d(200%, 0, 0);
    -ms-transform: translate3d(200%, 0, 0);
    -o-transform: translate3d(200%, 0, 0);
    transform: translate3d(200%, 0, 0);
}
.tabs > *:first-child + * + * + * {
    -webkit-transform: translate3d(300%, 0, 0);
    -moz-transform: translate3d(300%, 0, 0);
    -ms-transform: translate3d(300%, 0, 0);
    -o-transform: translate3d(300%, 0, 0);
    transform: translate3d(300%, 0, 0);
}

Note: I tried using relative positioning instead of the transforms above, and I was still getting the same weirdness I described above.

You may be interested in these books.

(paid link)

As an Amazon Associate I earn from qualifying purchases.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.