w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.5k stars 661 forks source link

[css3 positioning] support position:sticky inside an overflow:hidden|auto on general parents #865

Open hunboy opened 7 years ago

hunboy commented 7 years ago

css3 position

Currently the position:sticky element exclusively works when the all of general parents are overflow:visible. This is problematic a little whilst we use overflow:hidden trick for clearfix etc.

Testcase attached. Here is a simplesample, how is the layout broken when we change the overflow to visible, however sticky starts to work in that case. I detected, all of general parent-elements in the parent-path must be overflow: visible, which is weird a little. Any overflow:hidden in the parent-path kills the sticky.

ER: sync somehow the spec to allow sticky subcontainers inside a overflow:hidden general parent.

testcase: https://jsfiddle.net/utasir/rmmkxq62/11/

hunboy commented 7 years ago

mozbugzilla link : https://bugzilla.mozilla.org/show_bug.cgi?id=1329203

pedi commented 7 years ago

maybe not really a bug but by implementation. Check this specs out

http://www.coreyford.name/files/position-sticky-presentation/

The box’s position depends on its containing block (established as for position:static) as well as its scrolling container, defined by the nearest ancestor in the same document with a computed value for ‘overflow-x’ or ‘overflow-y’ other than ‘visible’, or the viewport if no such ancestor exists.

niutech commented 7 years ago

The official spec for position: sticky is at W3C and there is no mention of overflow: hidden. I do not see a reason why an element with position: sticky cannot be sticky inside <body> with overflow-x: hidden. Please fix it.

mstange commented 7 years ago

This spec seems to have changed quite a bit since the implementation in Gecko. It no longer mentions scrolling ancestors, it exclusively talks about containing blocks now. I agree that the current writing of the spec would allow crossing overflow:hidden / auto descendants when computing the offset. But I don't know what the rationale behind that change to the spec was, or whether implementing that change is web compatible.

sidhu663 commented 7 years ago

+1, I find this a very strange limitation especially when the element in question is not actually hidden. It makes it much more fragile to use it given that a change to a parent container / component could break sticky behaviour entirely

valtlai commented 6 years ago

It would be nice to make this work:

<div class="table-wrapper">
    <table>
        <tr><th>Foo</th><th>Bar</th></tr>
        <tr><td>sin</td><td>cos</td></tr>
        <tr><td>tan</td><td>baz</td></tr>
        ...
    </table>
</div>
.table-wrapper {
    overflow-x: auto;
}

th {
    position: sticky;
    top: 0;
}

See demo

I need both horizontal scrolling and sticky header.

SimplyPhy commented 6 years ago

I'm building a table template in SCSS which requires this functionality in many situations.

Thankfully, everything now works in Chrome [63], Edge [41], Firefox [59] and Safari [11]. Safari requires -webkit-sticky, and Firefox 59 is the current dev version (unreleased).

Here's a basic test case for overflow-x and overflow-y support: https://codepen.io/SimplyPhy/pen/oEZKZo

jonjohnjohnson commented 6 years ago

@SimplyPhy was going to comment and say safari just needed prefix for sticky, but now I see you updated your comment. Though safari still has an issue 'sticking' your td:first-child {top: 0}, I'm guessing because it doesn't calculate the top edge of it's parent tr like other browsers. But if you remove that property declaration, the behavior is probably how you want it?

SimplyPhy commented 6 years ago

Yup, thanks @jonjohnjohnson and nice find.

natematykiewicz commented 6 years ago

I have the following rule:

html, body {
  overflow-x: hidden;
}

and because of it, I'm not able to use position: sticky; at all. I'm trying to add some submit buttons on a form, and have them sticky to the bottom of the window position: sticky; bottom: 0; when outside the viewport. It works great and I love the feature, but this overflow-x rule prevents it from working. It sounds very similar to what @niutech mentioned.

jonjohnjohnson commented 6 years ago

@natematykiewicz though not ideal, I believe all current browser versions support sticky behavior if you alter your DOM structure so that the scrolling element is inside the body/html, filling out the screen. This does make it so browsers that resize the viewport on scrolling of the document.scrollingElement won't resize, such as ios safari. Notice the example code @SimplyPhy provided with for his table utility. Hope that helps until the spec is figured out.

natematykiewicz commented 6 years ago

@jonjohnjohnson my previous comment was that I have overflow-x: hidden; on the body and html tag, and that prevents position: sticky; from working on anything. I did just realize that if I put the overflow-x: hidden; on only the body or the html (but not BOTH), then it works (this is in Chrome on a Mac). So, maybe I can actually adjust my CSS to get the best of both worlds. It's just odd that preventing the body and html from horizontally scrolling means that I can no longer get elements to stick to the top of the screen. I could see overflow-y: hidden; causing problems, but not overflow-x: hidden;.

jonjohnjohnson commented 6 years ago

@natematykiewicz

The computed values of both overflow-x & overflow-y are...

as specified, except withvisible computing to auto if one of overflow-x or overflow-y is not visible - css-overflow-3/#overflow-properties

This is what causes our issue and is spec compliant.

Furthermore, check your "(but not BOTH)" solution in safari, including iOS, because that approach hasn't historically given people what they have sought out. -> overflow-xhidden-doesnt-prevent-content-from-overflowing.... Again, the most robust solution is to scroll an element WITHIN the body, not the body/html/document. Good luck.

jonjohnjohnson commented 6 years ago

Just wanted to make a note for others about how allowing general overflow: hidden ancestors to not be the "scrolling context" for a sticky element isn't always desirable. When using a modal view that programmatically turns off/on scrolling with hidden/auto a sticky element would jump around the screen between the swapping scrolling contexts.

Also, when we (eventually) have overflow: clip implemented everywhere, this should no longer be an issue?

sudheer-gowrigari commented 6 years ago

@SimplyPhy in your codepen (https://codepen.io/SimplyPhy/pen/oEZKZo ) on the table,Is there any way to restrict the scroll on header content. Scroll looks odd on the header.

moritzjacobs commented 6 years ago

For anybody stumbling upon this issue via google, here's a workaround (at least for my case) using a polyfill: https://stackoverflow.com/a/52420677/4903358

protoEvangelion commented 5 years ago

For my use case, one of the ancestors in the DOM had overflow-x: hidden;. My goal was to simply hide horizontal scrollbar, so this may not apply to your use case.

The way I fixed it was removing overflow-x: hidden; from that element in the DOM and adding it to body instead!

body {
  overflow-x: hidden;
}
damienroche commented 5 years ago

@protoEvangelion I realized after trying that the solution does not work on mobile browsers :( https://stackoverflow.com/questions/14270084/overflow-xhidden-doesnt-prevent-content-from-overflowing-in-mobile-browsers

jonjohnjohnson commented 5 years ago

@damienroche I believe androids webview behaves according to spec here, but it's iOS that doesn't. If you want to chip in and voice concern about their noncompliance here's the public ticket.

WebKit Bugzilla - Bug 153852 - with overflow:hidden CSS is scrollable on iOS

OliverJAsh commented 5 years ago

Also, when we (eventually) have overflow: clip implemented everywhere, this should no longer be an issue?

It does sound like overflow: clip is the answer here. What is the current position of this standard? I don't see any browsers implementing it. 🤔

emilio commented 5 years ago

Gecko implements overflow: -moz-hidden-unscrollable which is effectively overflow: clip. Don't know the background on why it's not unprefixed.

Baedda commented 5 years ago

The fact that overflow: hidden; on an ancestor makes this container become the new scrolling container for the sticky element, takes away the opportunity to clip the sticky container in any way.

I have a use case where the height of the sticky element and the height of its parent are unknown. I want the sticky element to be sticky on browser scroll. But I dont want the sticky element to overflow its parent. With the current spec this seems to be impossible. But I believe that this is a common use case.

I could fix my problem for Firefox by using overflow: -moz-hidden-unscrollable;. But what would be the valid way to do this?

joma74 commented 5 years ago

No clue for in combination with scroll-snap on parent. Page with 100vh per section, sticky as dynamic css for header nav part of first section overflows the scroll bar.

Additionally toying with width and max-width values and units leaves me the impression that browser chrome comes up with magical t.i. spec-undefined values.

WangXBruc commented 5 years ago

It would be nice to make this work:

<div class="table-wrapper">
    <table>
        <tr><th>Foo</th><th>Bar</th></tr>
        <tr><td>sin</td><td>cos</td></tr>
        <tr><td>tan</td><td>baz</td></tr>
        ...
    </table>
</div>
.table-wrapper {
    overflow-x: auto;
}

th {
    position: sticky;
    top: 0;
}

See demo

I need both horizontal scrolling and sticky header.

Hello. Have you found the solution? I have the same require with yours

valtlai commented 5 years ago

@WangXBruc Unfortunately not. 🙁

jonjohnjohnson commented 5 years ago

@valtlai @WangXBruc I don't exactly condone this fix, but if you really need some sort of nested separate scroller with separate axis treatment, this is the best approach yet. https://uxdesign.cc/position-stuck-96c9f55d9526

@OliverJAsh @emilio Gecko bug for renaming/implementing overflow: clip. https://bugzilla.mozilla.org/show_bug.cgi?id=1531609

@Baedda Tangentially...

The fact that overflow: hidden; on an ancestor makes this container become the new scrolling container for the sticky element, takes away the opportunity to clip the sticky container in any way.

There are other ways to "clip" the sticky container, by using clip-path, even though the "overflow" can affect the geometry of scrollable content, it still behaves well in most cases.

@joma74

No clue for in combination with scroll-snap on parent. Page with 100vh per section, sticky as dynamic css for header nav part of first section overflows the scroll bar.

You can use calc() to offset a "fullscreen" height with a sticky nav. But some browsers (iOS safari) don't report 100vh as the height of the unscrolled viewport. (https://nicolas-hoizey.com/2015/02/viewport-height-is-taller-than-the-visible-part-of-the-document-in-some-mobile-browsers.html)

@Baedda @joma74 check these demos for examples of sticky/clip-path/calc()/vh

UPDATE Demos are best viewed in webkit, which has the most robust clip-path support.

joshjg commented 5 years ago

@jonjohnjohnson The link you posted (https://uxdesign.cc/position-stuck-96c9f55d9526) requires javascript (basically, attaching a scroll listener which updates the positioning of other elements). I don't think this is an acceptable workaround, especially from a performance perspective.

JacobDB commented 5 years ago

@joshjg I agree – it's a solution, but given that position: sticky is a native CSS feature, we shouldn't need to rely on JavaScript for this use case.

jonjohnjohnson commented 5 years ago

@joshjg @JacobDB I provided that link in response to a case which is a bit different/more than this filing, so I'd suggest opening up a separate issue. Something along the lines of...

"[css-positioned-3] expand sticky logic to allow sticky positioning behavior between nested scrollers, specifically cross axis"

Especially since it seems a lot of folks have shown support for the use case @valtlai provided.

BenMorel commented 5 years ago

I want to emphasize my support for @valtlai's request as well:

.table-wrapper {
    overflow-x: auto;
}

th {
    position: sticky;
    top: 0;
}

Currently you have to choose between having the table scroll horizontally, or sticking the headers. You can't have both, which is a shame.

The only "solution" that does not involve JavaScript is forcing the table to a given height, which is a really ugly and counter-user-friendly solution with its 2 scrollbars, one for the page and one for the table (especially when the table is the central UI element in the page).

natematykiewicz commented 5 years ago

I tried to do sticky headers on a table once for a nice UX improvement. I gave up for the very reason you just said. The table was in a div to allow for horizontal scrolling and therefore could not have its headers stuck...

Saw-mon-and-Natalie commented 4 years ago

Looks like if you set overflow to a non-default value for the sticky element ancestor, you would need to set the ancestor height as well. Otherwise, the position: sticky would not work. For example:

.wrapper {
    overflow-x: hidden;
    height: 100vh;
}

This works on the chrome 78 (64-bit) on Win 7. Example: codepen

er-parag91 commented 4 years ago

It would be nice to make this work:

<div class="table-wrapper">
    <table>
        <tr><th>Foo</th><th>Bar</th></tr>
        <tr><td>sin</td><td>cos</td></tr>
        <tr><td>tan</td><td>baz</td></tr>
        ...
    </table>
</div>
.table-wrapper {
    overflow-x: auto;
}

th {
    position: sticky;
    top: 0;
}

See demo I need both horizontal scrolling and sticky header.

Hello. Have you found the solution? I have the same require with yours

I was facing same issue, I did try number of CSS solution and finally it works the way you guys are looking for. Here is working codesandbox URL:

https://codesandbox.io/embed/objective-frog-m4imr?fontsize=14&hidenavigation=1&theme=dark

And I am using react table however CSS styling is pretty easy to understand and Don't thing you would face any issue copying over solution even though you don't know react.

BenMorel commented 4 years ago

@er-parag91 It looks like your code is an example of the "solution" I mentioned above:

The only "solution" that does not involve JavaScript is forcing the table to a given height, which is a really ugly and counter-user-friendly solution with its 2 scrollbars, one for the page and one for the table (especially when the table is the central UI element in the page).

You're forcing the table to 50vh height. If you have a scrollable page, you end up with 2 scrollbars: one for the page, and one for the table. Ugly if you ask me!

melihoeztuerk commented 4 years ago

Facing same issue. Well seems like this will still not be supported:

Any ancestor between the sticky element and its user-scrollable container with overflow computed as anything but visible/clip will effectively prevent sticking (https://caniuse.com/#feat=css-sticky)

I would prefer to search for a nice workaround instead of a "clean" solution.

tabatkins commented 4 years ago

As far as I can tell, this request is based on using overflow:hidden to force a containing to become a BFC so it'll clearfix.

Since we (a) have better ways to force a BFC now that don't interact badly with position:sticky (such as display: flow-root;), and (b) rarely if ever need to clearfix now (since that's mostly an artifact of float-based layouts), I think we can safely close this as no change.

coreyworrell commented 4 years ago

@tabatkins hmm, you may have missed the multiple comments about sticky headers on horizontally scrollable tables (a common method for handling large tables on smaller devices). There is also the issue of the common case of having a site wrapper that hides any overflow-x for off screen navigation and similar animation techniques. That makes it impossible to use sticky elements.

shrpne commented 4 years ago

@tabatkins in my practice overflow is rarely used for BFC, BFC is a thing from the past, when layout was made with float, for now, we have flex or even grid and developers have no reason to use float and care about BFC.

I agree with @coreyworrell, 90% of my websites have overflow wrapper to hide offscreen things like protruding images.

JacobDB commented 4 years ago

I agree – I typically set overflow: hidden; on a container to prevent my column system from overflowing. A very simplified example is below.

.row {
    display: flex;
    width: calc(100% + $column_gap);
}

.col {
    flex: 1 1 25%;
    padding-right: $column_gap;
}

This method keeps everything aligned just right with an accurate gap between each element, but of course means that the .row almost always overflows the width of its containers. To combat this, I set .page-wrapper { overflow: hidden; }, which works great, except when I need to use something with position: sticky;.

Admittedly, my use case could be solved by switching to grid instead of flex, but in my testing, I've been unable to replicate the features I need using grid.

tabatkins commented 4 years ago

hmm, you may have missed the multiple comments about sticky headers on horizontally scrollable tables

Hmm, I had. I see the issue now.

It seems like the case here is wanting to put horizontal scrollbars on a too-wide table, but not put vertical scrollbars on it when it's taller than 100vh. (Or generally "whatever the height of the screen is", since that's a... difficult-to-predict value on mobile.) And then still get the header rows to be sticky as the table scrolls off, but stay contained to the table itself as normal.

Okay, I'll re-open for this issue.

tabatkins commented 4 years ago

For the "I'm just using an overflow:hidden wrapper to prevents things from visually spilling out" case, overflow:clip achieves that without making it scrollable, and so shouldn't affect stickyness. That value isn't implemented in Chrome yet, but -moz-hidden-unscrollable is a prefixed name for the value, and if I'm reading previous comments correctly, it does indeed work with sticky (doesn't block stickiness). So I'll consider that part of the issue solved.

(The naming of "hidden" is unfortunate; really it's an "I'm handling scrolling with JS, don't worry about it" value, but it claimed a useful name that makes it sound more general than it is. What we're now calling "clip" should have been named "hidden". ^_^)

saschanaz commented 4 years ago

The naming of "hidden" is unfortunate; really it's an "I'm handling scrolling with JS, don't worry about it" value, but it claimed a useful name that makes it sound more general than it is.

Maybe a new alias overflow: manual? 🤔

tabatkins commented 4 years ago

but not put vertical scrollbars on it when it's taller than 100vh. (Or generally "whatever the height of the screen is", since that's a... difficult-to-predict value on mobile.)

Expanding on this, is the problem just that a max-height on a table is hard to predict on mobile? (Because 100vh might be too large depending on what UI is being shown.) If that's the case, would #4329 solve the issue?

coreyworrell commented 4 years ago

Expanding on this, is the problem just that a max-height on a table is hard to predict on mobile? (Because 100vh might be too large depending on what UI is being shown.) If that's the case, would #4329 solve the issue?

@tabatkins Not quite. I think you said it correct before:

It seems like the case here is wanting to put horizontal scrollbars on a too-wide table, but not put vertical scrollbars on it when it's taller than 100vh. (Or generally "whatever the height of the screen is", since that's a... difficult-to-predict value on mobile.) And then still get the header rows to be sticky as the table scrolls off, but stay contained to the table itself as normal.

Adding a max-height can cause double scroll bars (one for window, one for table). It is preferable to allow the table it's auto height.

jonjohnjohnson commented 4 years ago

@tabatkins & @coreyworrell I think you are talking about what I addressed in https://github.com/w3c/csswg-drafts/issues/865#issuecomment-488522567.

"[css-positioned-3] expand sticky logic to allow sticky positioning behavior between nested scrollers, specifically cross axis"

(And honestly, there is a strange precedent for nested sticky logic that shares a containing block, see http://output.jsbin.com/zeyanef/7, best in webkit/firefox. Though, this example doesn't share the containing block through a scroll container, but instead through sticky ancestors.)

This seems to be the only remaining issue in the thread and should simply be broken out, allowing this thread to be closed. Because we have overflow:clip solving the general ancestor between the sticky element and it's scroll container (containing block) issue. And browsers have now consolidated on a solution for scrollingElement weirdness with horizontal overflow between the html & body elements, where an author just needs to make sure body isn't an intermediate scroll container between the sticky element and the html scrollingElement.

Final note, it really would be a bad idea to retroactively make overflow:hidden elements do what most in this thread desire for the reasons I stated in https://github.com/w3c/csswg-drafts/issues/865#issuecomment-387908285.

When using a modal view that programmatically turns off/on scrolling with hidden/auto a sticky element would jump around the screen between the swapping scrolling contexts.

This shows exactly why the specified behavior of overflow:clip is what is really needed, it just needs to get implemented everywhere. Elements with overflow:hidden are still scrolling elements even if only programmatically.

shuklaayush commented 4 years ago

Is there a workaround for what @valtlai mentioned? (https://github.com/w3c/csswg-drafts/issues/865#issuecomment-350585274) The link that @jonjohnjohnson shared does not seem to work for native html tables. I too don't want to add a fixed height since the extra scrollbar makes things ugly.

johannesodland commented 4 years ago

For the "I'm just using an overflow:hidden wrapper to prevents things from visually spilling out" case, overflow:clip achieves that without making it scrollable, and so shouldn't affect stickyness. That value isn't implemented in Chrome yet, but -moz-hidden-unscrollable is a prefixed name for the value, and if I'm reading previous comments correctly, it does indeed work with sticky (doesn't block stickiness). So I'll consider that part of the issue solved.

@tabatkins: Chrome has closed their issue as won't fix: https://bugs.chromium.org/p/chromium/issues/detail?id=566226

Maybe we should push for them to reopen the issue?

Fuseteam commented 4 years ago

for anyone looking for a solution for native html table i have posted on stackoverflow here:https://stackoverflow.com/questions/63384685/overflowauto-doesnt-work-as-expected-with-positionsticky and also a jsfiddle here: https://jsfiddle.net/uf468ocy/ basically it has a copy table with only the headers which is shown as the header is scrolled out of position.

it possible way i was thinking to make it work better to have ti work natively i would think to put sticky op the scrolling container with overflow such that when the container is scrolled out of view the top "sticks" while the actual container continues to scroll.......if that makes sense

for example say i have a div #data like this

<div id="data" style="overflow:auto;position:sticky;top:0;">
<div id="sticky-element" style="position:sticky;top:0;">
</div>
</div>

then we scrolling would stick data to the top of viewport instead of scrolling it past, while the content of data is then scolled which sticks the element at the top of the data which is the top of the screen or something like that

curremtly if the sticky element is larger than the view port it just scrolls past

payalord commented 4 years ago

How about this solution: https://uxdesign.cc/position-stuck-96c9f55d9526

konkit commented 4 years ago

How about this solution: https://uxdesign.cc/position-stuck-96c9f55d9526

AFAIK, it assumes, that columns have a constant width. Otherwise, if a wider content changes the proportions of columns in the table, then headers can misalign with the content.

(Fork of the codepen from the article with an example of such situation: https://codepen.io/konkit/pen/rNLjjwL)