Position fixed and CSS transforms
An HTML element with position: fixed
will lose its fixed positioning if a CSS transform is applied to its ancestors. Remove all transforms from ancestors to recover fixed positioning.
I had a long standing and mysterious bug in Birdview.js (press Z or pinch-in to see Birdview in action): after activating Birdview, elements with position: fixed
would no longer stay fixed relative to the viewport.
Recently, I stumbled on a compilation of tips by Claus Wahlers about how to make something 100% height on Mobile Safari, and I came across this bit:
If positioning turns out not to be relative to the viewport, you probably have a transform applied on an ancestor, and your element is positioned relative to that ancestor’s bounds.
Here’s the full quote from the W3C spec:
For elements whose layout is governed by the CSS box model, any value other than none
for the transform also causes the element to become a containing block, and the object acts as a containing block for fixed positioned descendants.
Indeed! That’s what’s happening with Birdview.js!
Birdview applies a CSS scale and translate to 2 containers that wrap the whole document. When the user exits Birdview, the wrappers are set back to scale(1)
and translateY(0px)
. But even an identity transform is still a transform. So any element with position: fixed
would become fixed relative to the wrappers instead of the viewport.
I could rewrite the JavaScript calls to set back the wrappers to transform: ''
, but I need to set a specific value to get back to, so that all browsers can correctly interpolate and animate the transition.
The solution is to remove CSS transforms only after the transition ends, using the transitionend
event:
wrapper.addEventListener("transitionend", function(){
wrapper.style.transform = '';
}, false);
Links
- Claus Wahlers, Mobile Safari’s 100% Height Dilemma, 2016.
- W3C, CSS Transforms Module, 2017.
- Birdview.js