w3c / csswg-drafts

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

[css-position-3] Sticky position and margins (should margins be considered at all?). #9052

Open bfgeek opened 1 year ago

bfgeek commented 1 year ago

https://drafts.csswg.org/css-position-3/#stickypos-insets

For each side of the box, if the corresponding inset property is not auto, and the corresponding border edge of the box would be outside the corresponding edge of the sticky view rectangle, then the box must be visually shifted (as for relative positioning) to be inward of that sticky view rectangle edge, insofar as it can while its position box remains contained within its containing block. The position box is its margin box, except that for any side for which the distance between its margin edge and the corresponding edge of its containing block is less than its corresponding margin, that distance is used in place of that margin.

The spec and implementations are currently a little scattered when it comes to how margins should be treated for sticky-pos elements. It's not clear if all the interactions have been thought through completely.

  1. Implementations (and the spec I think?) currently use border-box rect to determine if something is overconstrained (e.g. position: sticky; left: 100px; right: 100px;. Is this intentional/correct?

  2. Implementations currently differ on if auto-margins should be considered. For example: https://www.software.hixie.ch/utilities/js/live-dom-viewer/?saved=11848

    <!DOCTYPE html>
    <div style="overflow: scroll; width: 100px; height: 100px;">
    <div style="margin: 20px; border: solid; width: 100px; height: 100px;">
    <div style="position: sticky; left: 0; width: 10px; height: 10px; margin-right: auto; background: lime;"></div>
    </div>
    </div>

Per-spec Firefox is currently correct - however this might be surprising to web-developers as it'll effectively opt-out the element being sticky by default if they are just using auto-margins to center content.

  1. Unclear from the spec how collapsing margins should be considered in the calculations above. Currently no implementations appear to look at collapsing margins.

  2. The sticky view rectangle is currently defined as the border-box which seems to match what web-developers would expect. E.g. you don't expect the sticky-element to stick based on some unseen margin. Do the margins in the rest of the calculation add create more confusion than value? E.g. margins are often used for aligning content, if you use margins in this way and then make the element sticky-pos then it'll likely have un-expected behaviour (see auto-margins above as one example).

TL;DR Should the "position box" just be the border-box instead? (rather than the margin-box).

flackr commented 1 year ago
  1. Implementations (and the spec I think?) currently use border-box rect to determine if something is overconstrained (e.g. position: sticky; left: 100px; right: 100px;. Is this intentional/correct?

I think this is correct, since margins are not maintained from the sticky view rectangle. For example, consider the following (demo):

<style>
.scroller {
  height: 400px;
  overflow: auto;
  border: 1px solid black;
}
.sticky {
  margin: 250px 0;
  height: 200px;
  position: sticky;
  background: green;
  top: 0;
  bottom:0;
}
.spacer {
  height: 200px;
  background: #ccf;
}
</style>
<div class="scroller">
  <div class="spacer"></div>
  <div class="sticky"></div>
  <div class="spacer"></div>
</div>

The sticky element is not overconstrained. It can in fact be pushed up / down into the margin reserved between it and the adjacent spacers until it is 250px away from the top/bottom of the container / scroller. That said I still find it confusing that we try to preserve its margins from the container.

  1. Unclear from the spec how collapsing margins should be considered in the calculations above. Currently no implementations appear to look at collapsing margins.

IMO if we consider margins we should allow for them to collapse.

  1. The sticky view rectangle is currently defined as the border-box which seems to match what web-developers would expect. E.g. you don't expect the sticky-element to stick based on some unseen margin. Do the margins in the rest of the calculation add create more confusion than value? E.g. margins are often used for aligning content, if you use margins in this way and then make the element sticky-pos then it'll likely have un-expected behaviour (see auto-margins above as one example).

TL;DR Should the "position box" just be the border-box instead? (rather than the margin-box).

I agree, I think it would be simpler if the sticky adjustment were only constrained by the border box, and this might enable margins to be used to set the initial position. However, given that we seem to have compat on this part (with non-auto margins) there's a chance this will be breaking. In particular when the sticky element is first I could see developers being surprised if it gets pushed up into its top margin, or if last being pushed down into its bottom margin.

css-meeting-bot commented 1 year ago

The CSS Working Group just discussed [css-position-3] Sticky position and margins (should margins be considered at all?)., and agreed to the following:

The full IRC log of that discussion <fantasai> iank_: I was looking into sticky positioning optimizations
<fantasai> iank_: noticed that margins were a little bit weird, quirky, in implementations
<fantasai> iank_: so wanted to ask what was the original intention with margins, and should we change it
<fantasai> iank_: currently today there are 3 rectangles we care about
<fantasai> iank_: 1. scroll container
<fantasai> iank_: 2. sticky view rectangle (which is usually the parent of the stickypos rectangle)
<fantasai> iank_: 3. stickypos element itself
<fantasai> iank_: Most of the stickypos calculations don't use margin box, they use border box of stickypos element
<fantasai> iank_: e.g. you scroll to the top, sticks the border box
<fantasai> iank_: this seems desirable
<fantasai> iank_: where margins come in is they don't affect stickypos eleent itself
<fantasai> iank_: it affects sticky view rectangle, reduces by the margins
<fantasai> iank_: it has some unfortunate effects
<fantasai> iank_: margins are used for positioning
<fantasai> iank_: so e.g. if using auto margins, stickypos no longer works for that element
<fantasai> iank_: also if you set margins, it may not go all the wya down to stickypos rectangle when using for positioning
<fantasai> iank_: Comments? Thoughts?
<Rossen_> q?
<fantasai> iank_: atm, Blink and WebKit would ignored auto margins (treat as zero)
<fantasai> iank_: so only Firefox is affected by that part
<fantasai> emilio: so blink and webkit are already doing what you want to propose?
<fantasai> iank_: what I think might be better is to ignore margins completely from stickypos calculations
<fantasai> iank_: from examples I've seen online, no one is using margins for the sticky pos rectangle interaction
<fantasai> iank_: if we do want to keep margins around, there's a question about how auto vs fixed should work
<fantasai> iank_: blink and webkit have other issues, e.g. uses margins that don't exist (e.g. on table cells)
<fantasai> flackr: also don't respect margin collapsing
<fantasai> flackr: if we use margins, we should use margin collapsing
<fantasai> flackr: weird if switching to position:sticky made margins act differently
<TabAtkins> fantasai: breaking this down, i think we can def take a resolution that auto margins are 0 for stickypos
<Rossen_> ack fantasai
<flackr> sgtm
<TabAtkins> fantasai: are there any objections to that? it not we can just resolve that and move to the rest of th esubissues
<TabAtkins> smfr: so my original thoughts for sticky is that when it's not constrainted by the scroller it behaves identical to relpos
<TabAtkins> iank_: it doesn't
<TabAtkins> smfr: so only when it's being affected by scrolling are we doing a different margin behavior?
<TabAtkins> iank_: yes
<TabAtkins> (i think i misscribed what ian's "it doesn't" was responding to)
<TabAtkins> iank_: and we're happy to see if this is web compatible
<TabAtkins> fantasai: so we think there's a question of what do auto margins do, i think we all agree it should be treated as zero
<TabAtkins> fantasai: then another question of whether margins should be used at all for stickypos positioning
<TabAtkins> fantasai: and if we continue to honor the margins, do we use collapsed or not
<TabAtkins> fantasai: And I think the right answer for that is to use the boxs own margins and descendants but not siblings/parents
<TabAtkins> iank_: I'd prefer to avoid the complexity of amrgin collapsing entirely, we don't store enough info for block-start of our boxes
<TabAtkins> iank_: So my pref i sto ignore margins completely
<TabAtkins> q+
<emilio> q+
<TabAtkins> fantasai: I think it's reaosnble if you're doing margins to use only the element's own margins
<Rossen_> ack TabAtkins
<fantasai> TabAtkins: given the constraint, which is correct, that in absence of doing stickypos should act like relpos,
<fantasai> TabAtkins: margins should work, collapse, etc.
<fantasai> TabAtkins: If you scroll slightly, don't want to jump
<fantasai> TabAtkins: don't want to change behavior of margins at that point
<fantasai> TabAtkins: only reasonable behavior is to either do complicated partial collapsing
<fantasai> TabAtkins: or don't care about margins at all
<fantasai> TabAtkins: only use the border box
<fantasai> TabAtkins: that's consistent with how we choose margin-box vs border-box
<fantasai> TabAtkins: when complicated margins we use border-box, because that's the only reasonable thing to do
<fantasai> TabAtkins: stickypos shifting is solely on whether border box is escaping or not
<Rossen_> ack emilio
<fantasai> emilio: does that change negative margins on sticky element? because that seems like a reasonable thing to do
<fantasai> emilio: I'm ok trying this, but... I'm a bit concerned
<fantasai> emilio: if you're willing to test compat, sure
<fantasai> iank_: when I play with examples, I don't see people using margins
<emilio> fantasai: If we're gonna honor margins I don't think you want to honor collapsing margins
<emilio> ... you want to honor just the element's own margins
<emilio> ... and if the element's own margins collapse with the parents then you treat that as zero
<emilio> iank_: impl wise I don't particularly want to do that
<emilio> ... can of worms
<emilio> TabAtkins: if you were collapsing a margin and you're sufficiently close to the top of the scroller
<emilio> fantasai: it's not about the scroller, it's about the CB and the viewport
<emilio> ... in that case you're adding to your margins
<emilio> ... but if you want margins and they collapse you zero-them out
<fantasai> e.g. if the element has 3px margin, use that to define spacing between containing block edge and the stickpos
<emilio> ... anything we do with margins that ignore clearance it's going to impose a change from sticky to relpos
<emilio> ... I'd rather not do that
<emilio> iank_: fwiw for other scroll related features they use the scroll-margin
<TabAtkins> (the "anything we do" and following line is me, not fantasai)
<flackr> My demo in https://github.com/w3c/csswg-drafts/issues/9052#issuecomment-1639234115 shows a margin from the CB
<emilio> fantasai: I think this means that if you want spacing among things but not when sticky you need to use padding
<emilio> ... and margin won't work anymore
<emilio> TabAtkins: yeah, that's right
<emilio> fantasai: we should be pretty clear about that implication
<fantasai> s/spacing among things but not when sticky/spacing between a stickypos and its containing block even while it's shifting around by scrolling/
<flackr> q+
<emilio> iank_: I think folks are using margin-trim... ??? (missed)
<astearns> ack fantasai
<emilio> ... I could see the interaction if we used the margin box of the sticky pos but this weird mixed model is weird
<Rossen_> ack flackr
<emilio> flackr: I think that the way it behaves right now is conflating margin in two different contexts, the relpos layout is collapsing margin but the sticky margin is wrt the containing block
<emilio> ... I'd rather have the zero interpretation, having these two interpretations is awkward
<emilio> PROPOSED: Don't consider margins in sticky-pos calculations
<emilio> RESOLVED: Don't consider margins in sticky-pos calculations
<emilio> <br type=lunch>
dholbert commented 1 year ago

FWIW, I filed these Gecko bugs: https://bugzilla.mozilla.org/show_bug.cgi?id=1844435 on ignoring margins for sticky-positioning (pending evidence that that's web-compatible) https://bugzilla.mozilla.org/show_bug.cgi?id=1844438 as a smaller intermediate step of ignoring auto margins (which is a smaller step that seems more clearly web-compatible given that that's what Blink/WebKit already do, as discussed in the first comment here)

johannesodland commented 1 year ago

The dual implication of margin on sticky elements has been an issue and if it's web compatible it will be good to clean up that ambiguity. However, the margin has been our only way of controlling the "sticky constraint rectangle" (or the "sticky view rectangle" as it was called in the discussion).

jonjohnjohnson has described a couple of use cases for controlling the SCR in the issue, but I'll add another here for reference:

Use case for controlling the sticky constraint rectangle ### Article with sticky headers An article has multiple sections, and each section has a sticky header element. When the user reach the bottom of each section, the header element should be pushed out of view, before the next section and header element is scrolled into view. Currently, the solution for limiting the sticky constraint rectangle is to set a margin-bottom on the sticky element, and then negating that margin on the following sibling: ```html
...
...
...
...
``` ```css header { position: sticky; margin-bottom: 90vh; /* Limit SCR */ } header + * { margin-top: -90vh; /* Negate effect of margin bottom */ } ```

As it's been resolved to ignore margins in sticky-pos calculations, can we find another way to control the SCR? Something similar to scroll-margin?

I know we have content that use margin to control the SCR, and we will have to find other solutions when this is implemented. The only solution (that I can think of) that is supported in browsers today is to add padding to the block level ancestor, and then negate that in the child nearest to the padding edge. That feels even worse than what we do today.

jonjohnjohnson commented 10 months ago

I respectfully disagree with this resolution and hope it ends up not web-compatible, primarily on the grounds that negative margins have allowed sticky elements to stay "stuck" (with fixed-like behavior) in webkit/gecko during overscroll animations.

See https://github.com/w3c/csswg-drafts/issues/8309#issuecomment-1443059377

I also second much of @johannesodland in https://github.com/w3c/csswg-drafts/issues/9052#issuecomment-1809658376

johannesodland commented 3 months ago

Returning to alternative solutions for defining the "sticky constraint rectangle":

Currently the sticky positioned element is shifted to stay within the sticky view rectangle insofar its possible while staying within its containing block inset by its margins (simplified). In other words, sticky pos operates with two insets, one inset from its scrollport, and one inset from its containing block. If margins are no longer to be considered, authors have no way of controlling that second inset.

I'm wondering if the box insets could be extended to support multiple named insets, to give a way of controlling the inset from the containing block?

/* 
Make element sticky against scrollport 
insofar as it can while staying 20px within its containing block 
*/
position: sticky;
inset-block: 
  scrollport 0px,
  containing-block 20px;

This could solve issue one in https://github.com/w3c/csswg-drafts/issues/2496.

(A position-container property as mentioned in https://github.com/w3c/csswg-drafts/issues/9868 could solve issue 2 by allowing you to change the containing block.)