w3c / csswg-drafts

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

[css-align][css-position][css-anchor-position] Introduce "document containing block" for some purposes? #10861

Open tabatkins opened 1 month ago

tabatkins commented 1 month ago

[another continuation from #10859]

(Apologies for the length of this, there's a lot of context needed to make it understandable. There's a "Proposal" heading further down if you wanna just skip to the idea first.)

Anchor Positioning, in general, makes the actual bounds of the abspos containing block much more important than they used to be. This isn't a huge deal if the CB is generated by an element, or is the viewport - both produce meaningful rectangles with relevant sizes and positions. But I keep running into issues when the CB is the ICB.

The ICB's size is reasonable - it's the size of the viewport, which is perfectly fine as a default available space for the abspos, often actually good. It's the ICB's position that's nonsensical for many purposes - it lives at the top of the document, aligned with 0,0 in the root scroller. On most pages, then, the position of most elements is completely outside of the ICB, meaning that for things which care about the relative position of the CB and an anchor element (such as position-area), you get annoying, meaningless interactions way more often than in the other cases.

For example, if you use position-area: bottom, your IMCB is the space between your anchor's bottom edge and the CB's bottom edge. If your CB is the ICB, and your element is somewhere further down the page, then these two edges are in the wrong order, and produce a negative-height rectangle. Anchor Pos handles this fairly gracefully, and causes the "CB bottom" edge to be "pulled down" by the anchor-bottom edge, so we get a zero-height IMCB flush with the anchor's bottom, but that's still not a useful IMCB. You immediately overflow, for example, which triggers fallback positioning.

What you actually want in this case, I think approximately 100% of the time, is a CB that is "the height of the document". That is, if your anchor is in the very last paragraph, very close to the bottom of the document, you expect your available space to be small and maybe trigger fallback positioning; but if your anchor is in the middle of the document, you expect there to be ample space below you.


As an example of how wrong the current behavior goes, imagine a normal document, like a spec, full of multiple screenfulls of text. An anchor element is right in the middle of the document, well outside the initial viewport, but with thousands of pixels open above and below it. An abspos anchors to it, and uses position-area: right;, getting a default vertical alignment of anchor-center.

The actual IMCB for this element stretches from the top edge of the document (the top of the ICB), down to the bottom edge of the anchor element. (It wants to stretch to the bottom of the ICB, but that's way up top, well above the anchor, so the anchor drags the line down to its own bottom edge.) Now, the default alignment safety kicks in - if the abspos is taller than the anchor, centering it would overflow the IMCB, so it shifts upward until its bottom edge is flush with the anchor's bottom edge.

This shifting only happens if the anchor is initially offscreen. If the anchor is initially on screen, "above the fold", then it sees plenty of room above and below it, and centers properly. But, because the anchor is initially well outside the bounds of the ICB, the IMCB is artificially shrunken on one side, and the "centered" element is now permanently frozen in an un-centered position.

(Note that it does not correct itself if you scroll down so the element is on the screen! None of its edges or coordinates care about the current scroll offset; it's an abspos. It's just weirdly off-center because it was initially off-screen, and it stays weirdly off-center permanently.)


Even outside of Anchor Positioning, it's not unusual for people to want to abspos something to the bottom/right of "the content", either on the root scroller or within a scrollable element. This currently isn't possible directly - you have to use a wrapper relpos inside the scroller. If the abspos uses the scroller itself as its abspos containing block, it gets the equivalent of the ICB - a rectangle the size of the scrollport, positioned at the origin.


Proposal

  1. We define a new default containing block, the "scrolling content block". This is, roughly, the height of the document. I talked this over with @bfgeek and the best option he knows of that's currently available and won't require adding memory to every element is the "interior padding edge", aka the size of a wrapping div around the content. This ignores other absposes, and transforms, both of which do affect the "scrollable overflow rect" and which we don't want, both for cyclic and compositing reasons.

    Anything using the "scrolling content block" as its CB still gets an available space that's the size of the scrollport, so a 100% width or height is still reasonably sized. But its bounds for positioning purposes is larger.

  2. When using position-area, in any situation where the abspos would today use the ICB as its containing block, or a scrollable element as its containing block, it instead uses the "scrolling content block".

  3. Separately, we figure out some way to expose this to normal positioned elements, too. Possibly a sub-property of position. Until then, authors can hack it by using position-area: all with any valid position-anchor, even if they're not actually using anchor positioning for anything.


The definition for "scrolling content block" above, notably, doesn't make things any better when people are using "fake root scrollers", aka setting html { height: 100%; overflow: auto; }. The definition I gave will then tightly wrap around the html element, resulting in a rect more or less identical to the ICB. This isn't great, but it's exactly the situation we're in today for every element. So we'll still be in a better situation for most things, and no worse of a situation for these things in particular. If @flackr ever revives his idea of allowing other elements to provide the root scroller, that'll fix this issue as well.

tabatkins commented 1 month ago

If we don't do this, then to avoid the alignment badness in my example, I think we have to change the default alignment to be unsafe in all cases when the CB is the ICB or a scroller. This means that you'll overflow if you're very close to the top or bottom of the content, but you'll get reasonable alignment in all other cases.

Accepting this change means we can keep the medium-safety alignment, as we'll get correct alignment by default in these cases, and can still shift to avoid overflow when the anchor is very close to the top or bottom.

css-meeting-bot commented 1 month ago

The CSS Working Group just discussed [css-align][css-position][css-anchor-position] Introduce "document containing block" for some purposes?, and agreed to the following:

The full IRC log of that discussion <kbabbitt> TabAtkins: Anchor positioning exposes details of the containg block a lot more than abspos did
<kbabbitt> ...generally this works just fine, inset-area cares about how it's positioned relative to cb
<kbabbitt> ...or if it's outside, it's an exceptional case and is also handled fine
<kbabbitt> ...exception is when you're fixpos, because then you end up using the ICB which while is reasonably sized, is not a reasonably positioned rect
<kbabbitt> ...ICB just sits at the top of the document
<fantasai> scribe+
<kbabbitt> ...and most of the document is outside of that rectangle
<kbabbitt> ...which means that almost all of your acnhors need the exceptional behavior of anchor outside of CB
<kbabbitt> ...while we have as reasonable as we can behavior, it's not ideal
<kbabbitt> ...all an error case and always recovering
<kbabbitt> ....all you really want for something like an element acnhored to something far down is to avoid flowing outside the bounds of the doc
<kbabbitt> ...e.g. offscreen
<kbabbitt> ...and that doesn't work well in the case we have here
<kbabbitt> ...what you want is a CB that is the size of the visible document
<kbabbitt> ...what that means is part of this issue
<kbabbitt> ...My proposal is that this sort of request is useful outside of anchor positioning
<kbabbitt> ...relatively common for people within a scrollable area to position in the bottom right area of ths scrollable content
<kbabbitt> ...can't do that today wiothout an additional wrapper
<kbabbitt> ...if you go to the bottom right with insets and it positions you in th ebottom right of the scroll container not the scrollable content
<kbabbitt> ....people work around that today by using a replos wrapper around all of their scrollable content
<kbabbitt> ...so their abospos can go to the bottom right of the scroll container
<kbabbitt> ...making this behavior easier to opt into, and making anchor positioning generally work better when your CB is the document is worth fixing
<kbabbitt> ...my proposal is that we have a new concept: the document containing block
<kbabbitt> ...which is a rectangle size to contain all the stuff in the document
<kbabbitt> ...we use that by default for anchor position ing if we would use the ICB
<kbabbitt> ...also have some way to explicitly opt normal abspos into being able to reference this concept both for document and for any scrollable content
<kbabbitt> ...two big questions: first what is this rectangle, how do you define it in a way that's reasonbly chjeap
<kbabbitt> ...iank_ has an answer that works in Chrome and sounds approximately right
<kbabbitt> ...don't know how generalizable that is
<Rossen0> q+
<kbabbitt> iank_: when you calculate the scrollable overflow for a scroll container
<kbabbitt> ...you need to know where the end ? is
<kbabbitt> ...and that's one of the calculations that goes into the whole scrollable area
<kbabbitt> ...e.g. for grid this is defined as the end of the grid
<kbabbitt> ...this is relatively cheap, the calculation is only based on the in-flow children at that point
<emilio> q+
<kbabbitt> ...w/o transforms, usually what people wnat for this calculation
<kbabbitt> TabAtkins:: other questio would be how we can let normal abposes opt into it
<kbabbitt> ...probably a position subproperty, don't want to solve for it now, can still devault anchor positioning to right default today
<kbabbitt> Rossen0: I missed the reasoning about, does top line get redefined as well?
<miriam> q+
<kbabbitt> ...so that when you scroll, that positioned element also gets scrolled with it?
<kbabbitt> TabAtkins: No, the DCB will start still at the top left of the entire ducoument just like ICB, will just grow downward and rightward to contain entire contents of the page
<kbabbitt> iank_: not growing into negative space beyond the scrollable region
<kbabbitt> ...there are cases .eg in rtl container where right would stay 0 and flips like that
<Rossen0> ack Rossen0
<Rossen0> ack fantasai
<kbabbitt> fantasai: curious about the definition in the inline axis
<kbabbitt> ...this totally makes sense, we probably want a similar concept for non-document scrollers
<kbabbitt> ...agree with most of what you're trying to do here
<kbabbitt> iank_: easiest thing is unify behavior when we fixed the end point for the scrollable behavior
<kbabbitt> TabAtkins: I'm calling it DCB, need a better name
<kbabbitt> ...intend it applies equally to other scrollers, if your anchor is inside a scroller you want same behavior
<miriam> q- (I was going to ask about applying to other scrollers)
<miriam> q-
<kbabbitt> fantasai: you're suggesting for inline axis we calculate padding ??? I agree with that
<Rossen0> q?
<Rossen0> Rossen
<kbabbitt> ...have to think about exactly where else it would be used, using it for anchor positioning seems reasonable
<Rossen0> ack Rossen
<kbabbitt> iank_: when I as doing webdev stuff, I ran into this problem constantly
<flackr> +1
<kbabbitt> emilio: agree this a good thing in general
<kbabbitt> ...esp when you get into edge cases e.g. rtl
<kbabbitt> ...having something that consistentl covers scrollable areas gets tricky
<fantasai> s/we calculate padding ???/we use the point where we attach the inline padding?/
<kbabbitt> ...2 questions, one: with regards to scrollable padding we add, you mean that CB would be included or excluded?
<kbabbitt> iank_: included
<kbabbitt> emilio: so it would cover everything, sounds good
<kbabbitt> iank_: we're not changing the fact that abspose references padding box by default
<Rossen0> q+
<kbabbitt> fantasai: any box that is scrollable when it gerneates a CB, genreates 2 CBs, inner and outer
<Rossen0> ack emilio
<kbabbitt> ...I guess it generates 3, you have the content one too
<kbabbitt> emilio: if you have something that overflows that cb, you have something that scrolls down
<Rossen4> q?
<kbabbitt> iank_: the reason the padding edge definition is useful is, you don't need to calculate the entire scrollable area
<kbabbitt> iank_: based on flow content this is where the padding edge is, then you run abspos layout
<kbabbitt> iank_: then you gather up scrollable area
<kbabbitt> fantasai: this comes from when we decided to add padding to scrollable ??? ... it only considers input
<TabAtkins> s/input/in-flow content/
<kbabbitt> emilio: sounds good
<fantasai> s/???/area/
<kbabbitt> iank_: that ? is also nice because it doesn't consider transforms
<TabAtkins> s/?/box/
<kbabbitt> emilio: wonder if there are use cases like, I want to ? the whole scrollbar
<kbabbitt> iank_: [missed]
<Rossen4> ack fantasai
<Rossen4> ack Rossen
<kbabbitt> fantasai: we've got each box generates 3 CBs, inflow CB which is content area, local CB which is this padding edge that takes over the area that scrolls, then outer CB which is the background attachment scroll box
<kbabbitt> ...we need better terms for these
<kbabbitt> ...in your comment you suggested position area would be one way to capture this entire area
<kbabbitt> ...that reads really well
<kbabbitt> ...issue says only when there's a valid anchor
<kbabbitt> ...do we make position area do it always since it doesnt rely on anchor>
<kbabbitt> TabAtkins: potentially. I feel like that's a bit hacky and probably wnat CB control to be done via a more focused properly, more things in that vein you want to select between
<kbabbitt> ...somewhat uninclined to fix things in this hacky way but could be convinced in another issue
<kbabbitt> fantasai: making it contidional on the validity of anchor is a little weird
<kbabbitt> ...making it not that would be great, I have a few ideas
<kbabbitt> Rossen4: the position area bounds you define is based on the static content only but it doesn't include any abspos content?
<kbabbitt> TabAtkins: correct, no abspos no transforms
<fantasai> I'm thinking maybe `position-area` always applies, it's just that if you have no valid anchor then you always get the entire area
<kbabbitt> Rossen4: what about relative
<kbabbitt> <crosstalk>
<kbabbitt> iank_: it does not include relative
<kbabbitt> iank_: when it comes to adding padding to scrollable end, we have basically alignment on that
<TabAtkins> q?
<kbabbitt> Rossen4: sounds like a very useful feature
<kbabbitt> TabAtkins: suggest resolution: Adopt the concept of document containing block (better name TBD) into position-4 and have anchor positioning use it by default instead of ICB
<kbabbitt> Rossen4: additional comments or objections?
<flackr> +1
<kbabbitt> fantasai: we should proably actually publish position-4
<kbabbitt> RESOLVED: Adopt the concept of document containing block (better name TBD) into position-4 and have anchor positioning use it by default instead of ICB
<kbabbitt> [discussion about contents of position-4]
<kbabbitt> Rossen4: any objection on publishing FPWD of position-4?
<kbabbitt> RESOLVED: Publish FPWD of position-4
tabatkins commented 2 weeks ago

All right, defined. @fantasai and/or @frivoal , mind reviewing?

I went with "scrollable containing block" as the name, since "document containing block" was bad.