Re: [csswg-drafts] Alternate masonry path forward (#9041)

tldr: We think the performance characteristics of the current Grid-based Masonry spec are unshippable, and more or less unfixable while the two layout modes are entwined. But we think we can get a lot closer to agreement on the feature set than it appears the editors currently believe, if we can agree to make this a separate display type. In particular, every single one of the examples in the recent Safari blog post is completely fine in our preferred approach (despite the blog post asserting they aren't, because it's arguing against a much simpler version). See the very end for our alternate proposal; the rest of this is arguments for why we don't like the current and want something different.

----------

The "masonry" behavior is currently defined directly on top of Grid, as a variant behavior similar to "subgrid". We (the Chrome and Edge team's engineers) find the feature valuable and want it to progress, but have come to the conclusion that this is not the right way to define this behavior.

Some of these issues were brought up [back in 2022, in issue 8206](https://github.com/w3c/csswg-drafts/issues/8206). In [July 2023, in Issue 9041](https://github.com/w3c/csswg-drafts/issues/9041), we raised the concern more generally, and suggested an alternate approach. Since then we've opened several additional issues about more specific concerns with the current approach (or had them opened, spinning out specific concerns from 9041):

* [Issue 9321](https://github.com/w3c/csswg-drafts/issues/9321), about repeat(auto-fit/auto-fill) with intrinsic sizes (reasonable and desirable in Masonry, very difficult in Grid)
* [Issue 9326](https://github.com/w3c/csswg-drafts/issues/9326), about tracks sized with different intrinsic sizes causing quadratic layout behavior in Masonry (but fine in Grid)
* [Issue 10053](https://github.com/w3c/csswg-drafts/issues/10053), about the track sizing algo (when given different intrinsic sizes) actually going exponential in Masonry when nesting grids.

These issues can generally be reduced to a single conflict: at their most fundamental level, Grid and Masonry are opposite wrt sizing and placement. Grid places all items before layout, and then has complete knowledge of what items are in any given track, so it can do complex intrinsic sizing based on that knowledge. Masonry places items as they're laid out, and thus it cannot know what elements will end up in any given track, and can't do the same complex intrinsic sizing (but as a result gains the potential for some behavior that Grid can't easily accommodate).

fantasai has attempted to address these concerns, by defining special "pre-placement" layout behavior - laying out each item in every possible position to obtain intrinsic measurements, and then using those measurements to size the tracks before starting placement. However, this doesn't fundamentally fix the issues, it just changes what the behavior is quadratic on: rather than being quadratic on the number of items, the complexity scales with (number of items) * (number of tracks). The exponential behavior from nesting grids also remains under this behavior. Also, this is still fundamentally different from what Grid does, raising the question of why they're considered the same layout mode when they use such different layout algorithms.

Given that we know authors like to push the boundaries of layout modes, and in particular often create grids with a very large number of tracks (sometimes to produce something like Masonry layout, but not always), we don't think this fix is acceptable. We extra don't believe exponential layout behavior is acceptable, as it causes layout to visibly stutter quickly, with only a handful of reasonably-sized grids involved. All of these can be avoided with some reasonable restrictions that we think still allow a wide range of useful Masonry behaviors, but which would be awkward to apply in the general Grid framework.

Additionally, as stated in Issue 9321, we think there are some useful behaviors that Masonry can allow that are infeasible to apply to Grid, such as repeat(auto-fill, auto). (Doing so in Grid causes essentially the same issues that were described for Masonry, above.)

Finally, we have a general design objection to attempting to merge Masonry and Grid into a single layout concept. The two layout modes absolutely have many conceptual similarities, and we can and should lean on those to ensure that they can work well together in obvious ways, but each has enough unique peculiarities that it just gets awkward to define what works for Grid, what works for Masonry, and what works for both. I believe this situation to be similar to that of Block and Multicol - these were folded into a single layout mode, and ever since we've had to deal with odd inconsistencies between the two in what behaviors they expect. If we had defined display: multicol back in the day, many issues would have been avoided. I think the Grid/Masonry marriage is even more fraught with inconsistencies - heck, they fundamentally don't and can't share a layout algorithm - and would like to avoid us repeating mistakes we made in the past. As we move forward, every new addition to Grid and Masonry will have to be evaluated to see if it applies to one or both modes, and when it applies to both, what distinctive quirks it has in each.

--------------

So the above is our general objections to the current approach, and address fantasai's first and third bullet points (whether Masonry fits in Grid conceptually, and whether integrating the two models is a pro or a con). I'll go a little more into detail about these in a sec, but first, let's discuss their second bullet point (whether track size variation is needed/useful, and how we can deal with the perf implications).

As we've stated before, existing JS masonry implementations don't generally seem to offer the ability to do distinct track sizes - all tracks the same width seems to be the overwhelming common case. That said, having differently-sized tracks does seem reasonable; [Jen's blog post on Friday](https://webkit.org/blog/15269/help-us-invent-masonry-layouts-for-css-grid-level-3/) had a bunch of good examples.

There's a lot more flexibility between "all tracks the same" and "any intermixing of tracks like Grid can do", tho, and we think there's a reasonable sweet spot that allows a lot of useful cases without running into the perf issues we find so objectionable.

Basically, the perf issues arise only when you mix an intrinsically-sized track with any different-sized track. 

* If everything is fixed/flexible, that's trivial - the tracks are just based on the available space, at worst, and don't care about the item sizes. 
* If everything is the same intrinsic size, it's less trivial but still fine - you do a pass thru the entire items list and measure them for that track size. If an item spans multiple tracks, you just subtract gutters and divide by the span. Then you have the track size, and can start laying out items into that now-fixed size. We do similar things for plenty of other layout modes, this is fine.
* If there's, say, a fixed and an auto track, tho, we run into the issues. If everything was span-1 this would still be no problem, it would basically be equivalent to the previous case just with a little more bookkeeping, but when you have spans, you now have to worry about every overlapping combination of tracks, and run into that "measure them in every position" behavior as the only possibility.

In other words, every example given in Jen's blog post is just fine. Those are all totally doable, no objections here. We just object to the possibility of writing, say, grid-template-columns: 50px auto 100px;, because in degenerate cases (which are more common than one would suppose), they cause quadratic or even exponential layout behavior.

--------------

Now back to bullet point 3 - is integrating the two models a pro or a con.

We think the cons outweigh the pros. There are simply too many places where some property is only valid for one or the other, or worse, some values for a shared property are only valid for one or the other. Above I outlined why a perfectly valid grid-template-columns would be problematic for Masonry, but on the other side, Masonry is perfectly fine with allowing masonry-tracks: repeat(auto-fill, auto), which is so disallowed in Grid that it's a syntax error. These sorts of syntax distinctions are easier to understand and remember when they're across similar but separate properties; when it's a single property that just has a different mix of valid values depending on the value of a second property, it gets confusing. And we don't think it's reasonable to expect authors to develop an intuitive understanding of this; the reasons for some values being usable in Grid vs Masonry depend on fairly deep layout-engine knowledge that we don't generally expect authors to possess.

(We'd also have to define what happens when they do write the wrong thing. We can't reject it at parse time, since its validity depends on the computed value of another property. We'd have to guess at some fallback behavior. This is already present in the spec, with auto-fit being invalid in Masonry, and treated as auto-fill instead. It would be better if this could be rejected at parse time.) 

Both layout modes have properties that don't apply to the other: grid-auto-* for Grid, masonry-threshold (and masonry-auto-flow, if we keep that).

Worse is grid-template-areas, which could conceptually apply to Masonry (giving names to tracks or groups of tracks), but can't in practice because of the 2d nature of the syntax. We'd have to define a masonry-areas to port the concept over.

The end result is that authors have an arbitrary-feeling list of restrictions in what properties can be used for one or the other - properties with a masonry-* prefix obviously only work for Masonry, but some grid-* properties work too, and others don't, and for the ones that do work, sometimes only certain values work. If they were more cleanly separated layout modes, with all the Masonry properties having a masonry-* prefix even when they're similar to Grid properties, none of this is a concern.

----------

Finally, back to bullet point 1, whether Masonry and Grid fit together conceptually. We agree that they do! Many concepts are shared between them, and Jen's example of subgrid in masonry looks completely reasonable. We think that allowing subgrid items to take on a Masonry parent's lines is perfectly reasonable, with the caveat that the subgrid items interact with the Masonry track sizing in a Masonry-like way (as described above, for the case where the Masonry is using a repeated intrinsic track size; when the tracks are fixed/flexible, the issue's moot already).  Similarly, if we defined some sort of "submasonry" value, using those in Grid parents would be reasonable, with similar restrictions on how they interact with Grid sizing.

And generally, for any concept for which Grid and Masonry share the idea, they should both have properties for it, with as identical of a syntax as possible. Helping authors transfer knowledge between the two layout modes is absolutely a good idea.

-----------

So, long post, apologies, we're at the end. Here is a very rough sketch of the proposal as we'd prefer to see it. I'm happy to spend time putting together a UD for this if it's easier to review that way, I just didn't have time to do so.

```
/* on the masonry container */
display: masonry;

masonry-template-tracks: [ <line-names> ? [ <fixed-or-flexible-size> | <fixed-or-flexible-repeat> ] ]+ <line-names>? ]
        | [ <line-names>? <intrinsic-repeat> <line-names>? ]
masonry-template-areas: (like one row of grid-template-areas)
masonry-direction: row | column | row-reverse | column-reverse
   /* initial value column, contrasted with flexbox's initial row */

justify/align-*: (same as Grid, but with some restrictions depending on masonry-direction)

masonry-threshold: [(as discussed in 9328)](https://github.com/w3c/csswg-drafts/issues/9328)
masonry-auto-flow: (as in the current spec)

/* on the masonry items */
masonry-track: <grid-line> [ / <grid-line> ]?
masonry-track-start: <grid-line>
masonry-track-end: <grid-line>

justify/align-*: (same as above)
```

+ a definition for how subgrids work in Masonry, and possibly a subgrid value for masonry-tracks that creates a sub-masonry, with definitions of how that works in both Masonry and Grid.

We're flexible on all the details, save for those I talked about in the preceding text of this comment.

-- 
GitHub Notification of comment by tabatkins
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/9041#issuecomment-2075210820 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Wednesday, 24 April 2024 15:24:25 UTC