'transform3d' not working with position: fixed children
Asked Answered
E

10

157

I have a situation where, in normal CSS circumstances, a fixed div would be positioned exactly where it is specified (top:0px, left:0px).

This does not seem to be respected if I have a parent that has a translate3d transform. Am I not seeing something? I have tried other webkit-transform like style and transform origin options but had no luck.

I have attached a JSFiddle with an example where I would have expected the yellow box be at the top corner of the page rather than inside of the container element.

You can find below a simplified version of the fiddle:

#outer {
    position:relative; 
    -webkit-transform:translate3d(0px, 20px , 0px); 
    height: 300px; 
    border: 1px solid #5511FF; 
    padding: 10px;
    background: rgba(100,180,250, .8); 
    width: 80%;
}
#middle{
    position:relative; 
    border: 1px dotted #445511; 
    height: 300px; 
    padding: 5px;
    background: rgba(250,10,255, .6);
}
#inner {
    position: fixed; 
    top: 0px;
    box-shadow: 3px 3px 3px #333; 
    height: 20px; 
    left: 0px;
    background: rgba(200,180,80, .8); 
    margin: 5px; 
    padding: 5px;
}
<div id="container">
    Blue: Outer, <br>
    Purple: Middle<br>
    Yellow: Inner<br>
    <div id="outer"> 
        <div id="middle">
            <div id="inner">
                Inner block
            </div>
        </div>
    </div>
</div>

How can I make translate3d work with fixed-positioned children?

Edlun answered 4/3, 2013 at 3:53 Comment(1)
Same issue happens with filter too: https://mcmap.net/q/37397/-why-does-applying-a-css-filter-on-the-parent-break-the-child-positioning/8620333Paintbrush
C
230

This is because the transform creates a new local coordinate system, as per W3C spec:

In the HTML namespace, any value other than none for the transform results in the creation of both a stacking context and a containing block. The object acts as a containing block for fixed positioned descendants.

This means that fixed positioning becomes fixed to the transformed element, rather than the viewport.

There's not currently a work-around that I'm aware of.

It is also documented on Eric Meyer's article: Un-fixing Fixed Elements with CSS Transforms.

Counterproof answered 6/3, 2013 at 19:31 Comment(9)
Thanks, I hadn't noticed that the actual specification had that info..... I guess I got the equivalent to the mathematical answer: "by definition" :)Edlun
@INT, I don't think there's going to be a workaround for this. There's a major use-case for not allowing workarounds: user provided input could potentially cover controls outside of their designated area (think a malicious email adding options to the gmail toolbar). The best workaround would be to avoid transforms for the time being if you're going to use fixed from inside of it.Counterproof
Wouldn't setting the CSS top attribute to whatever window.scrollHeight is work? You might have to absolutely position it as well, but something like this should be doable, no? (too lazy to actually test right now)During
@bradorego you were right, I just added the code I used.Ream
This is still in flux in the spec as far as I can tell. See Bug 16328 - Use of "containing block" does not match CSS2.1 definition.Lupus
Easiest thing to do is have a wrapper div that has the fixed positioning with the transformed div within.Nausea
There is a workaround now, please mention it on your post. It's by rob.m 2 posts down.Tainataint
The workaround mentioned in previous comment does not seem to work anymore. Note that in IE11 the element stays fixed as expected.Convertible
@BradleyOrego I also believe that works, but with the limitation that if the fixed content overflows you still cannot scroll...Fanaticize
R
14

As Bradoergo suggested, just get the window scrollTop and add it to the absolute position top like:

function fix_scroll() {
  var s = $(window).scrollTop();
  var fixedTitle = $('#fixedContainer');
  fixedTitle.css('position','absolute');
  fixedTitle.css('top',s + 'px');
}fix_scroll();

$(window).on('scroll',fix_scroll);

This worked for me anyway.

Ream answered 31/1, 2014 at 18:59 Comment(4)
It works! but instead of binding to "window", I had to bind to the div that was scrolling. Also, the fixed element flickers.Gruchot
This could have done it, but like @Gruchot mentionned, it flickers.Cyanogen
Yeah, this doesn't update near fast enough to be clean enough for my usage :-/ good idea thoAfire
This is very bad practice, don't do style changes with jsZerk
E
12

I had a flickering on my fixed top nav when items in the page were using transform, the following applied to my top nav resolved the jumping/flickering issue:

#fixedTopNav {
    position: fixed;
    top: 0;
    transform: translateZ(0);
    -webkit-transform: translateZ(0);
}

Thanks to this answer on SO

Edomite answered 7/4, 2015 at 15:48 Comment(0)
C
6

In Firefox and Safari you can use position: sticky; instead of position: fixed; but it will not work in other browsers. For that you need javascript.

Colonial answered 14/12, 2015 at 18:45 Comment(4)
Sticky positioning is a hybrid of relative and fixed positioning, and it's really experimental, I'd highly recommend to avoid this, as it's not standard yet.Sulfa
AFAIK on Firefox and Safari you can simply use position:fixed and it would work as expected anyway.Loppy
@Loppy no, I have the issue when the parent uses translate3d and children's fixed position is flying around in some cases. Will try to use sticky while it' already supported by major browsers: caniuse.com/#feat=css-stickyHemocyte
sticky may be a solution,it works at modern browser.Salver
A
3

In my opinion, the best method to deal with this is to apply the same translate, but break children that need to be fixed out of their parent (translated) element; and then apply the translate to a div inside the position: fixed wrapper.

The results look something like this (in your case):

<div style='position:relative; border: 1px solid #5511FF; 
            -webkit-transform:translate3d(0px, 20px , 0px); 
            height: 100px; width: 200px;'> 

</div>
<div style='position: fixed; top: 0px; 
            box-shadow: 3px 3px 3px #333; 
            height: 20px; left: 0px;'>
    <div style='-webkit-transform:translate3d(0px, 20px, 0px);'>
        Inner block
    </div>
</div>

JSFiddle: https://jsfiddle.net/hju4nws1/

While this may not be ideal for some use cases, typically if you're fixing a div you probably could care less about what element is its parent/where it falls in the inheritance tree in your DOM, and seems to solve most of the headache - while still allowing both translate and position: fixed to live in (relative) harmony.

Afire answered 7/3, 2017 at 23:32 Comment(0)
E
0

I ran across the same problem. The only difference is that my element with 'position: fixed' had its 'top' and 'left' style properties set from JS. So I was able to apply a fix:

var oRect = oElement.getBoundingClientRect();

oRect object will contain real (relative to view port) top and left coordinates. So you can adjust your actual oElement.style.top and oElement.style.left properties.

Eulogium answered 22/7, 2013 at 12:29 Comment(1)
This works on IE and Chrome, but not on Android standard browser. The left is a number but it is always drawn on the position 0Otology
F
0

I have an off canvas sidebar that uses -webkit-transform: translate3d. This was preventing me from placing a fixed footer on the page. I resolved the issue by targeting a class on the html page that is added to the tag on initialization of the sidebar and then writing a css :not qualifier to state "-webkit-transform: none;" to the html tag when that class is not present on the html tag. Hope this helps someone out there with this same issue!

Flavouring answered 22/6, 2016 at 21:19 Comment(0)
D
0

Try to apply opposite transform to the child element:

<div style='position:relative; border: 1px solid #5511FF; 
            -webkit-transform:translate3d(0px, 20px , 0px); 
            height: 100px; width: 200px;'> 
    <div style='position: fixed; top: 0px; 
                -webkit-transform:translate3d(-100%, 0px , 0px); 
                box-shadow: 3px 3px 3px #333; 
                height: 20px; left: 0px;'>
        Inner block
    </div>
</div>
Derivative answered 6/8, 2016 at 13:59 Comment(0)
B
0

Add a dynamic class while the element transforms.$('#elementId').addClass('transformed'). Then go on to declare in css,

.translat3d(@x, @y, @z) { 
     -webkit-transform: translate3d(@X, @y, @z); 
             transform: translate3d(@x, @y, @z);
      //All other subsidaries as -moz-transform, -o-transform and -ms-transform 
}

then

#elementId { 
      -webkit-transform: none; 
              transform: none;
}

then

.transformed {
    #elementId { 
        .translate3d(0px, 20px, 0px);
    }
}

Now position: fixed when provided with a top and z-index property values on a child element just work fine and stay fixed until the parent element transforms. When the transformation is reverted the child element pops as fixed again. This should easen the situation if you are actually using a navigation sidebar that toggles open and closes upon a click, and you have a tab-set which should stay sticky as you scroll down the page.

Bouncing answered 30/7, 2018 at 19:8 Comment(0)
P
-1

One way to deal with this is to apply the same transform to the fixed element:

<br>
<div style='position:relative; border: 1px solid #5511FF; 
            -webkit-transform:translate3d(0px, 20px , 0px); 
            height: 100px; width: 200px;'> 
    <div style='position: fixed; top: 0px; 
                -webkit-transform:translate3d(0px, 20px , 0px); 
                box-shadow: 3px 3px 3px #333; 
                height: 20px; left: 0px;'>
        Inner block
    </div>
</div>
Phillipphillipe answered 3/9, 2015 at 4:7 Comment(1)
On IE bug does not exist in first placeLoppy

© 2022 - 2024 — McMap. All rights reserved.