Un-fixing Fixed Elements with CSS Transforms
Published 13 years, 1 month pastIn the course of experimenting with some new artistic scripts to follow up “Spinning the Web“, I ran across an interesting interaction between positioning and transforms.
Put simply: as per the Introduction of the latest CSS 2D Transforms draft, a transformed element creates a containing block for all its positioned descendants. This occurs in the absence of any explicit positioning of the transformed element.
Let’s walk through that. Say you have a document whose body
contains nothing except a position: static
(normal-flow) div
that contains some absolutely-positioned descendants. The containing block for those positioned elements will be the root element. Nothing unusual or unexpected there.
But then you decide to declare div {transform: rotate(10deg);}
. (Or even 0deg
, which will have the same result.) Now the div
is the containing block for the absolutely-positioned elements that descend from it. It’s as though transforming an element force-adds position: relative
. The positioned elements will rotate with their ancestor and be placed according to its containing block — not that of the root element.
Okay, so that’s a little unusual but perhaps not unexpected. I could make arguments both ways, and some of the arguments could get pretty complex. To pick one example, if the transformed element didn’t generate a containing block, how would translate transforms be handled?
Either way, here’s where things got really troublesome for me: a transformed element creates a containing block even for descendants that have been set to position: fixed
. In other words, the containing block for a fixed-position descendant of a transformed element is the transformed element, not the viewport. Furthermore, if the transformed element is in the normal flow, it will scroll with the document and the fixed-position descendants will scroll with it. You can see my test case, where the red and blue boxes would overlap each other and stay fixed in place, except the second green div
has been rotated.
Obviously this makes the fixed-position elements something less than fixed-position. In effect, not only does the transformed element act as if it’s been force-assigned position: relative
, the fixed descendants behave as if they’ve been force-changed to position: absolute
.
I find this not only unusual and unexpected, but also a wee bit unsettling. Personally, I think it goes too far. Fixed-position elements should be fixed to the viewport, regardless of the transformation of their ancestors. Of course, if you agree with my thinking there, realize that opens a whole new debate about how, or even whether, transforms of ancestors should be carried to fixed-position descendants.
I have my own intuitions about that, but this is definitely territory where intuitions are to be treated with caution. There are a lot of interacting behaviors no matter what you do, and no matter what you do someone’s going to find the results baffling in some way or other.
But since I do have intuitions, here’s what they are: transformed elements in the normal flow or floated do not establish containing blocks for absolutely- and fixed-position descendants. This means that any transforms you apply to the transformed element are not applied to the positioned descendants, because transforms don’t inherit.
What if you want a normal-flow transformed element to be a containing block? Use position: relative
, same as you would if there were no transform. And if you want the transforms to be passed on to the descendants even though no containing block is established? The inherit
value would work in some cases, though not all. That’s where my approach runs aground, and I’m not yet sure how to get it back to sea.
Okay, so that’s what I think. What do you think?
Comments (41)
I agree. Why would one style property (transform) create an exception in inheritance behavior for a different style property (postion)? Seems inconsistent with the rest of CSS, but maybe I’m overlooking existing CSS styles that do this very thing.
I’m with your gut on this one – especially given the use case with the fixed positioned elements.
I think behaviour should have to be explicitly coded by the CSS author, automatic stuff seems to screw people up, especially people just learning.
If the browser vendors can’t apply transforms *without* the element also being
position:relative
, then maybe the transform shouldn’t work unlessposition:relative
is also explicitly declared (this is how z-index works now I believe).When I read that transformed elements become offsetParents, I was baffled.
Who would possibly want this, and why? Explicit declaration of “position” takes care of this with no unexpected side-effects.
The CSSWG should be focusing their time on useful additions to transformations, such as a way to change discreet transformation functions a la carte.
Pingback ::
Elegant D » Eric's Archived Thoughts: Un-fixing Fixed Elements with CSS …
[…] Source: CSS – Google Blog Search […]
I ran into the issue earlier, and thought it was a browser implementation bug. Then again, I kind of got there by accident, so I didn’t think twice. Fixed position is sparsely used in practice, and it may be OK to not be completely logical, and have some incongruities where otherwise its difficult to figure things out to their logical end. I like the idea that these blocks create alternative coordinate systems and I like the idea of using absolute positioning within these altered coordinates. But I agree with you that if an element is fixed, it should be handed off to the viewport at whatever coordinate position it was fixed.
Hmm. Transform() doesn’t inherit, but it applies to the element and all its content. So all descendants of the transformed() element are e.g. rotated. That is similar to the way opacity works.
But that doesn’t square with your proposal about A.P and fixed pos. elements descendant of a transformed() element being exempted unless the transformed() element is rel. positioned…
Both behaviours can be equally unsettling or lead to unexpected results.
This is not the answer you expect, but I write it anyway.
One should not use static & fixed positioning. Never. Only trouble may occur if you use them and never anything positive. So feel free and add
* {position: relative}
somewhere at the beginning of your CSS and emulate fixed with absolute and you’ll be safe (and warm as a bonus).
PS! I personally use also started to use the following
* {overflow: hidden; box-sizing: border-box;}
and never looked back.
Pingback ::
2D Transforms in CSS3 | Web Directions
[…] […]
Pingback ::
Some links for light reading (21/9/11) | Max Design
[…] Un-fixing Fixed Elements with CSS Transforms […]
While I can understand the argument against this, as it stands this might be a “fly-swatter” that can be used to adjust for view-ports that “can’t” support position:fixed such as the iOS’ or ‘Droid?
As we all know the viewport on these devices is just a window overtop the site which allows us to see, and scale, the area below it … with these limitations the position:fixed falls over as the boudaries are not the page edges but rather the viewport’s. Wouldn’t using this little “splat” thereby allow us to transform our fixed elements back to the page and NOT to the viewport?
Or do I have this all wrong in my head?
Cheers for this one Eric, I’m off to create a test case now … results to follow!
Pingback ::
Robert’s read: links and suggestions, September 30th 2011 - Robert's talk
[…] Un-fixing Fixed Elements with CSS Transforms […]
The reason for this is that it’s hard to implement otherwise, not that it wouldn’t make sense. Check this excerpt from the spec:
Of course I agree with you that it’s very inconvenient and nonsensical.
I saw that “much harder to implement” bit, Lea, but I wonder: is it really? It seems to me like it would be fairly easy to define that a fixed-position element A) always has the viewport as its containing block; and B) it doesn’t inherit transforms or their effects (at a minimum) from its parent. Or that the transforms apply, but are computed relative to the containing block (the viewport).
I would have to agree with Onno, I was feeling it was a bug with the many different browsers I was using to view my content. It should be a fixed element.
I’m glad I’m not the only one who’s bemused by this behavior.
For those who want to experiment with it here’s a dabblet (btw thanks, Lea!): http://dabblet.com/gist/1723937
Is there any known workaround to keep the inner div fixed?
This was driving me bonkers. It was nice to find an explanation, though I was hoping for a workaround too… I may be able to substitute fixed for absolute in the case I am working on, though…
Is there any work around to keep the fixed position elements fixed?
Another bug/issue is caused by this: say I have a relative element next to a static element and they overlap. Relative element should always stay over the static element. However if I apply opacity (less than 1) or transform (other than none) then the static element is drawn over the relative element if the relative element is before the static element in DOM.
The fix is of course easy, just add z-index, but this just feels so wrong. I made a test page at CodePen.
I’ve found a way of working this out, but the results on ipad suck.
You can emulate the idea of fixed elements by using jquery to find the windows position related to the original position of the div, and assign the top styling to the div. Somehow you also need to get the height from the window height and assign it too since the 100% won’t work.
The result as I said return an emulation of a fixed element but when you scroll on a device like ipad, the images only goes to the position when scrolling stops.
Let me describe how I think of a browser interpreting HTML/CSS, and see if it changes your expectations. The way it used to work, on the first pass, the browser parsed and displayed each and every item in its own layer. Then it went back and collapsed both the explicit (z-index) and implicit (background, etc.) layers. The new CSS transpositions are a third pass in the middle. As before, on the first pass, the browser parses and displays each and every item in its own layer. Then the new second pass, whenever it finds transposition instructions, simply picks up all the pixels already drawn in that area of that layer and moves them. What it works with is _pixels_; the CSS is not relevant at that point. The browser has no idea which element drew which pixels, so it can’t skip doing anything with only certain pixels (or look “under” a pixel, since it’s working with only one layer at a time). That’s why subelements seem to always “inherit” no matter what you do, and why position “fixed” behaves unexpectedly: all the subelements are already drawn, and it’s simply the pixels that are transposed. Finally, as before, on the last pass the browser collapses all the layers.
Seems Firefox and Chrome still have the fixed is not really fixed behaviour, IE11 actually makes a fixed element really fixed to the viewport.
(BTW, care to declare a max-width to your Content? IE11 Metro in Fullscreen in 2560×1440 is kinda hard to read ;)
I’m on your side as well. We have a position property for a reason. transforms should not work unless we explicitly set position: relative. It’s the same with z-index.
As I checked in my IE11, the inner
position: fixed
element just stays fixed and ignores all those transforms. I think this approach better.I believe IE11 does that because the child elements do not inherit the transforms, so maybe there are other areas where it falls short, like fixed positioning. Ref: http://msdn.microsoft.com/en-in/library/ie/hh673529(v=vs.85).aspx
Chuck Kollar’s explanation does come a little closer to the issue, although it is incredibly irritating to have the ONE BELIEF about css positioning shaken! :P
Pingback ::
How to: Fixed Position but Relative to Container | SevenNet
[…] I did some research, and found that the issue was already been covered by Eric Meyer and even if it felt like a “trick”, turns out that this is part of the […]
Why does this still exist? I just spent hours trying to research and figure out this issue. Why would anyone want this be an intentional behavior?
Does anyone else think this is an implementation inconsistency?
Here is a link to a codepen with this effect in action and a button to toggle the transform property on the container. http://codepen.io/ericnkatz/pen/emWMLq
I think Chrome 40 or so does indeed copy the IE behaviour which is a sensible thing to do.
It is not out yet and I have not checked 40 and Firefox nor Safari seem to do this so yes, bad situation
I’ve been tooling around on codepen and have a solution that will work in some situations. Essentially it’s adapted from the technique for emulating fixed position using absolute positioning. This method does mean you need an extra container element that you apply the transform to, but c’est la vie for the moment. You can check out my pen here: http://codepen.io/isaiahmg/pen/wBxmmP
Isaiah, I looked at your codepen but it might fix it as far as positioning is concerned but it doesn’t fix the viewport as the containing block … or even simulate it. If you try and apply height 100% its still relative to the transformed parent…not the viewport like it should be if it was fixed (for real).
Carine. You’re right. Descendants of the container are still “trapped inside,” but at least with this technique you can still make something fixed within that context instead of losing that ability entirely when you apply a transform to something. I set my container to be the size of the viewport so that I could give them a fixed position where I wanted. Not a perfect solution by any means, but it allowed me to create the secondary navigation like I wanted to my site: http://isaiah.work
Yea I understand what you mean. Unfortunately my container is translated but I wanted the contents to grow and be positioned in the center of the viewport…so instead I’m going to give up the transform (was using new method of centering) and just use the old absolutes position centering instead, that way my fixed element will work. Out only does it on chrome(I don’t have safari to test) but not IE…haven’t tested yet in Firefox
Of course I don’t know what all browsers you need to support, but flexbox (http://flexboxin5.com/) is pretty well implemented across the board and is great for centering content horizontally and vertically.
I think that absolute positioned elements should be absolutely positioned relative to the first parent with position relative, and if a CSS transform is applied, NOTHING SHOULD CHANGE THAT.
I find it easier to reposition absolutely positioned elements when I want/need to, than to try and re-adjust after a CSS transform.
On the other hand, position “fixed” should do the opposite – fixed to the viewport. Always.
@Adrian von Gegerfelt That is exactly my opinion. The fact that a parent with a transform property even if the transform is set to none makes its position:fixed elements act as if they are absolute is very unintuitive and illogical to how the properties are supposed to work.
I agree, this caught me off guard today and it took me quite awhile to realize why my bootstrap modal (with fixed positioning) wasn’t showing up.
Pingback ::
Skrollr – Parallax Scrolling JavaScript Library
[…] or background-attachment:fixed inside elements which use CSS transforms as per CSS spec (http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/). That’s why those elements need to be outside of the skrollr-body […]
Glad I found this, just wasted hours trying to get a transparent overlay working using a pseudo ::after element. Looks like the only workaround is to use JS.
@Lea Verou: as far as I understood this bug submission in the spec, it means that the other behaviour being too hard to implement would be a sufficient reason, not that it is the actual explanation.
Please vote on W3 issue to get this fixed
https://www.w3.org/Bugs/Public/show_bug.cgi?id=16328
This is a very helpful post on Stack Overflow on all dependencies.
Stackoverflow about a list of all positioning descendants