57

There is a rendering issue with Google Chrome and Opera (why?=) with such code:

<html>
<style>
    #content {
        width: 150px;
        background: gray;
    }

    #sidebar {
        position: fixed;
        left: 200px;
        background: gray;
    }
</style>
<body>
    <div id="sidebar">
        <a href="#s1">Link #1</a><br/>
        <a href="#s2">Link #2</a>
    </div>

    <div id="content">
        <div id="s1">
            <a href="#s1">Link #1 TARGET</a>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit,
            sed do eiusmod tempor incididunt ut labore et dolore magna
            aliqua. Ut enim ad minim veniam, quis nostrud exercitation
            ullamco laboris nisi ut aliquip ex ea commodo consequat.
            Duis aute irure dolor in reprehenderit in voluptate velit
            esse cillum dolore eu fugiat nulla pariatur.
            Excepteur sint occaecat cupidatat non proident, sunt in culpa
            qui officia deserunt mollit anim id est laborum.</p>
        </div>
        <div id="s2">
            <a href="#s2">Link #2 TARGET</a>
            <ul>
                <li>Item 1</li>
                <li>Item 2</li>
            </ul>
        </div>
    </div>

    <a href="#">TOP</a>
</body>
</html>

As you can see, I am trying to make sidebar static on the right side. Everything works fine, until you add some <UL> tag on the page:

http://www.youtube.com/watch?v=zkhH6di2M0c

The fixed div sometimes starts to disappear when I click anchor links.

What can be done to avoid such behavior?

Qix - MONICA WAS MISTREATED
  • 12,202
  • 13
  • 73
  • 131
sunsay
  • 1,450
  • 2
  • 21
  • 28
  • I put this peace of code in a html5-boilerplate and had the same problems. Seems to be a bug in chrome. – Merec Mar 01 '13 at 08:59
  • 1
    I [put your code up on jsFiddle](http://jsfiddle.net/barney/aWwMk/) but believe it's not replicating… May be some clue there? – Barney Mar 04 '13 at 14:21
  • I've tryed jsFiddle too, and it seems that it's not replicating because of iframe wich is used by jsFiddle engine to display results. – sunsay Mar 05 '13 at 09:35

3 Answers3

169

Chrome solution:

Adding -webkit-transform: translateZ(0) to the #sidebar fixed the issue for me.

I've used translateZ(0) to fix numerous Chrome display bugs over the years. The rationale is that by invoking 3D transformation, re-paint is separated from the rest of the CSS pain stack (I can't provide much more detail than that, it's pretty much all Greek to me). In any case, it appears to work for me!

    #sidebar {
        -webkit-transform: translateZ(0);
    }

Opera solution:

This is not a generic solution (will need to be tweaked depending on the positioning requirements of the element in question). It works by forcing continuous repaints (via CSS animation) on a property that could affect layout (forcing other layout factors to be calculated and rendered, ie maintaining fixed positioning), but in practice do not. In this case, I've used margin-bottom, because there's no way that's going to affect your page layout (but Opera doesn't know that!):

@keyframes noop {
  0%   { margin-bottom: 0; }
  100% { margin-bottom: 1em; }
}

#sidebar {
    animation: noop 1s infinite;
}

Note: the solution is not perfect, in that (on my machine at least) the bug test cases will result in a minute flicker as Opera loses positioning and quickly redraws. Sadly I think this is as good as you will get, because as George says in his answer, this is Opera's natural behaviour between redraws — in theory my code makes redraw for the element continuous, but in practice there will be infinitesimal gaps.

EDIT 2 (2013-11-05): I've since encountered variations of this bug quite often. Although the original poster's reduced test case presents a perfectly legitimate bug, most occurences have been in situations where there is already a 3D transform operating on the body (or similarly high up the DOM tree). This is often used as a hack to force GPU rendering, but will actually lead to nasty repaint issues like this. 2 no-op 3D transforms don't make a right: if you're using one higher up the tree, try removing it first before adding another one.

EDIT 3 (2014-12-19): Chris reports that translateZ(0) doesn't work in some cases where scale3d(1,1,1) does.

Community
  • 1
  • 1
Barney
  • 15,464
  • 5
  • 56
  • 73
  • Yes it works for Chrome, but Opera and Safari still have this bug. – sunsay Mar 05 '13 at 10:06
  • I couldn't replicate the issue in Opera. What version are you using? And is the code posted above the entire HTML document? For the record, I'm using 12.14 build 1738 on Windows 7. I copied and pasted your code above into a UTF-8 encoded document and saved it as `index.html`, then opened it locally. Works like a charm! – Barney Mar 05 '13 at 10:12
  • http://www.youtube.com/watch?v=sKvUdOsoTXM&feature=youtu.be The file index.html has exactly your code above... – sunsay Mar 05 '13 at 10:44
  • It works! I have also added `-webkit-animation` to fix this issue in Safari. – sunsay Mar 07 '13 at 08:40
  • Wow this fix is awesome. The bug was driving me nuts for the past couple of days in Safari and Chrome. Thanks @Barney – michaellee Aug 08 '13 at 17:01
  • What do you mean @Lewis? The answer was accepted very shortly after being asked. – Barney Aug 20 '13 at 10:07
  • Ha! No I mean it's still causing a problem (in Webkit) 5 months from when this was posted. Why hasn't the issue been resolved in Webkit ;) – Lewis Aug 20 '13 at 13:18
  • @Lewis ah! Yeah I know — basic implementation of `position: fixed` won't work. What are we talking about here, IE6? ;) – Barney Aug 20 '13 at 13:29
  • Jeeze, I can't believe that actually fixed it. This is the first time that IE did right what Chrome did wrong -_- Has this issue been reported? – Xunnamius Aug 27 '13 at 21:49
  • 3
    using `-webkit-transform: scale3d(1,1,1);` seems to work in some other cases as well – CBarr Dec 09 '13 at 21:55
  • @ChrisBarr in practice, it's exactly the same thing: we're creating a 3D transform that has no effect. Both `scale3d(1,1,1)` and `translateZ(0)` will translate to `matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)`. – Barney Dec 10 '13 at 06:12
  • 2
    @Barney I know, but in my situation `translateZ(0)` did not fix the issue while `scale3d(1,1,1)` did for whatever reason. I'm not pretending to understand it, just gonna throw it out there as another option in case anyone else runs into the same thing. – CBarr Dec 10 '13 at 15:06
  • Not only is this still a problem for flickering, but it can also create a non DOM object if you scroll up and down from the top of the navigation very quickly. – Joseph Casey Oct 06 '14 at 18:04
  • What do you mean by 'non-DOM' object @JosephCasey? Something like the appearance of some rendered DOM that isn't actually there according to the DOM inspector? – Barney Oct 07 '14 at 11:38
  • @Barney I can't exactly confirm it since I can't access his browser at the time it was happening. Essentially, he used a null transform hack which enables the GPU to repaint a layer. This is only necessary when the browser is choking the CPU (common problem in Sticky Navs). When the CPU is unable to meet the demands being requested by the browser, a repaint issue will eventually occur for chrome and opera in which all dom elements are painted below their layers actual placement. This will result in the improperly painted elements being shifted down, possible out of sight. He needs to debounce. – Joseph Casey Oct 08 '14 at 16:44
  • 1
    @JosephCasey yeah, using a null transform higher up the tree is a common cause of this bug. Heaping a hack upon a hack is a bit of a code smell so I'd recommend ditching transform hacks altogether if this is the case (lower FPS is a better situation than buggy display). Can you elaborate on the debouncing approach? – Barney Oct 08 '14 at 16:49
  • @Barney If you look at the Twitter profiles, you'll see the null transform hack is used by them as well. However, the new approach will soon be using [{will-change}](http://caniuse.com/#feat=will-change) instead of Z indices. For sticky navs you should be debouncing the repaint to 5-40ms and separating that layer. [Here's an exmple](http://i.imgur.com/P2Ujwyx.jpg) of a sticky nav choking a cpu until the layers are painted below the actual layout. In this image the nav bar is actually still at the very top (where the pure red bar is) even though it shows as if it is down below. – Joseph Casey Oct 08 '14 at 16:49
  • @Barney Hack comes off dirty. It actually is industry standard to approach it this way. As I mentioned, the people at Flickr, Yahoo, and [Twitter](http://jsfiddle.net/qzganLau/) use Null Transform hack to fix the Opera and Chrome issue. In short, debounce is essentially grouping multiple events to prevent, almost always javascript, something from firing off so much. – Joseph Casey Oct 08 '14 at 16:58
  • 1
    Sure, requestAnimation frame, event throttling, etc. Here's hoping `position: sticky` gets popular soon. – Barney Oct 08 '14 at 17:12
  • 1
    ah, it's work for me, but it's looks like same as ie6 hacks :( – sarkiroka Jan 28 '15 at 11:38
  • @Barney In this example, if any child element (no matter how deep) of #sidebar has the problem, is adding the translateZ(0) to #sidebar enough? Or should we add to the parent of each problematic element? – Ethan Feb 17 '15 at 08:55
  • 1
    @Ethan I don't know. The problem in the first place is that 'normal' positioning logic starts to behave weirdly when nested 3d transformations are applied to structures where the elements need to reference each-others layouts to determine their own. Let us know if you discover any strange corner cases… – Barney Feb 17 '15 at 09:07
  • @Barney I was looking for a case where you have a matrix of N small images (200x200px, imagine ecommerce category page) and they have opacity. When lazyloading fills them up, how to handle the jitters in rendering? – Ethan Feb 17 '15 at 09:32
  • @Ethan my personal experience is that lazyloading is incredibly error-prone in conjunction with advanced positioning mechanisms – there are so many edge cases. In several cases where I've been in that scenario I have resorted to using `canvas` instead of `img` simply to get the necessary degree of control. In any case, you're probably better off filing a new SO question for your use case. – Barney Feb 18 '15 at 12:21
  • @Barney I was doing some research on this yesterday. The summary of the finding is that don't push too many jobs to GPU, there's VRAM crunch + other tasks that browsers themselves push there, leading to jitters (what you had observed). Best is to find out using chrome's timeline which ones are not meeting 60fps and then use transitions/opacity on them and push only them to GPU. So, it's ok to push either an element or a parent of a collection of elements to GPU, whichever is the necessity. The issue with VRAM crunch comes on mobile/responsive sites, so caution and testing there is important. – Ethan Feb 18 '15 at 16:57
  • 1
    This same concept works for similar issues in Firefox: transform: translateZ(0); – John Langford Apr 27 '17 at 14:38
  • 3
    Worked, I feel like it's 2002 again! – math0ne Jul 06 '17 at 17:05
  • 1
    Nothing worked for chrome. But transform: unset did the trick for me. – DeepSea May 28 '18 at 13:08
15

The key for Chrome is:

 html, body {height:100%;overflow:auto}

By adding this, the fixed position problem should be fixed.

Corneliu
  • 1,070
  • 1
  • 9
  • 16
  • This worked for me on Opera too, with the caveat that it produced double scrollbars, which is horrible. However, using a reset stylesheet (or by simply adding `margin:0;` to that rule), that problem disappears — this works! – Barney Mar 05 '13 at 11:29
  • 2
    Yeah, i have seen this solution in one of an old mailing lists... but i don't like that we can no longer use mouse scroll over fixed div and this fixed div (if we will set right:0) is shown above the right scrollbar :-( – sunsay Mar 05 '13 at 12:07
  • 2
    And we can not use Page Down/Up and cursor to scroll down/up until we click inside the page. – sunsay Mar 05 '13 at 12:15
  • 1
    When using this, the 'scroll' event stops triggering when scrolling the window. – Bruno Henrique Feb 19 '16 at 14:12
  • In my case I used ` html, body {overflow:auto}` then the `scroll` is still triggered AND the `position:fixed` works! – AymKdn Feb 04 '17 at 17:08
  • this worked best for me when I had css related to disabling side-scroll "bounce" effects on my `html, body` elements – Sgnl Jun 29 '17 at 01:57
0

It makes sense if you understand how Normal Flow of the document works. Let's say it's an edge case scenario.

There is no height declared in any element and #sidebar is taken OUT of the normal flow of the document by being position:fixed.

If you add a height property to #sidebar (pixels, not percentages) the problem is solved.

I would suggest including Normalize.css, I think it will take care of the bug.

George Katsanos
  • 12,325
  • 15
  • 55
  • 89
  • I add `height: 300px` as you said and the problem is not solved: http://sunsay.ru/shared/rb-0012.html Click links #2, #3 – sunsay Mar 07 '13 at 06:34
  • I clicked all links several times, no problem at all. I'm on: Version 24.0.1312.56 Ubuntu 12.10 (24.0.1312.56-0ubuntu0.12.10.3) . (only the link#1 got a bit cut (half) at some point but very minor – George Katsanos Mar 07 '13 at 07:38
  • I have just tried on the same version of chrome and ubuntu. The problem is still there. – sunsay Mar 07 '13 at 09:40
  • that's weird... so the problem for you is that the sidebar div is getting hidden/cut? – George Katsanos Mar 07 '13 at 10:34