Mobile Webkit reflow issue

mckamey picture mckamey · Jul 2, 2012 · Viewed 9.7k times · Source

I've been experiencing an issue in mobile versions of webkit (specifically Webkit 534.46 on iOS 5.1.1 as mobile Safari, and now Chrome for iOS) which doesn't happen on any desktop browser that I've seen. (i.e. the demos below should be viewed on a mobile version of webkit.)

Here is a live example of the issue. The core of the CSS is extremely straight forward. It positions an alphabet index along the left of the page:

#index {
    left:0; margin:0; padding:0; position:fixed; top:0; width:3em;
}

The issue happens when an element is fixed position over the top of the body. It is fully able to be interacted with until the scroll changes and then it stops accepting input. If I (manually) jiggle the scroll even one pixel then it becomes active again. The example was kept as simple as possible and does not use any JavaScript. After really hammering on it, I've discovered that it appears that the element thinks it is scrolled but has been visually fixed. In other words, if you click on 'A' then try to click on 'A' again, sometimes you will get a second click in but it will be further down the list. This seemed like a CSS reflow issue to me. I know that mobile webkit attempts to reduce the number of reflows.

Here is a live example of the workaround.

I am able to use JS to force the CSS of the entire document to reflow on scroll (with a throttle which prevents it from happening until 100ms after scrolling) which seems to workaround this issue in the simple example. Unfortunately, this does not help the real world version of this issue.

This is the code for the issue page and the workaround script.

My question is what is happening here and is there a CSS workaround that I am missing? Specifically, I'm curious if any CSS guru can figure out what the layout situation is that prevents the clicks from hitting the correct place on the fixed element? A better understanding might help find a real fix.

Edit: I forgot to mention that the example explicitly forces the viewport to the size of the window. So the user cannot zoom in/out, meaning that the position:fixed should anchor the element to the left side of the window.

Update (2012-09-20): This appears to be fixed in Mobile Safari on iOS 6 (as well as UIWebView). Any workaround should first check to make sure it is on iOS < 6. For example, using CssUserAgent this would look like:

if (parseFloat(cssua.ua.ios) < 6) { /* ... */ }

Answer

mckamey picture mckamey · Jul 13, 2012

The answer that actually solved my particular issue was a variation of a solution found in one of @Paul Sweatte's links:

Essentially, a plain div which is taller than the body is added. When it is removed, it causes the body to effectively scroll or reflow. Setting the delay to 0ms between adding/removing is enough to allow the DOM to recalculate without causing any flickering. This was the minimal script I could find which fully solved the problem for all position:fixed elements on my particular instance of this issue.

var hack = document.createElement("div");
hack.style.height = "101%";
document.body.appendChild(hack);
setTimeout(function(){
    document.body.removeChild(hack);
    hack = null;
}, 0);