w3c / csswg-drafts

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

[css-grid-1] Unpredicable gap positioning with two or more tracks. #7197

Open Tenkir opened 2 years ago

Tenkir commented 2 years ago

When defining a grid layout with multiple tracks of fractional units, the alignment of the grid-gap property with relation to the overall grid does not align to the expected behavior of the specification.

Take for example, a grid layout of

grid-template-columns: 50fr 50fr;
grid-gap: 10px;

This layout will split each column evenly, resulting in a 5px gap on the left most column, and a 5px gap on the right most column.

Screen Shot 2022-04-04 at 10 14 13 PM

However, expand this with a third column as such:

grid-template-columns: 50fr 25fr 25fr;
grid-gap: 10px;

This layout will split the two 25fr columns evenly, as expected. However, the gap between the 50fr column, and the 25fr column will not be even. Rather, the entirety of the gap will be on the 50fr side of the split.

Screen Shot 2022-04-04 at 10 19 27 PM

This is a significant issues with more complex layouts, as the gap between children becomes unpredictable, and alignment of any meta-elements becomes impossible.

Screen Shot 2022-04-04 at 10 20 10 PM

Spec: https://www.w3.org/TR/css-grid-1/

Minimum Reproduction: https://codesandbox.io/s/wild-leftpad-h2f782?file=/index.html

meyerweb commented 2 years ago

I gave your testcase a quick analysis and everything appears to be working as per the specification. The steps for laying out the grid cells are as follows:

  1. Determine the number of gaps and the total of their width. Here, because there are two gaps and you set a gap of 10px, that’s 20px.
  2. Subtract that total from the width of the grid container (.app). If the container is 1020px wide, that leaves 1000px, which I of course picked to make the rest of the math easy for myself.
  3. Divide that remaining width by the total of the fr values (100) to get the value of 1fr (10px).
  4. Multiply that amount by the number of fr per element. Here, that give us 500px, 250px, and 250px.
  5. Lay the elements and gaps out: 500px, 10px, 250px, 10px, 250px.

This is what happens in your testcase. So the left and right edges of the grid components are:

  1. .cell1: 0px, 500px
  2. first gap: 500px, 510px
  3. .cell2: 510px, 760px
  4. second gap: 760px, 770px
  5. .cell3: 770px, 1020px

The dividers, on the other hand, are primarily placed using percentage positioning. So the first is placed at 50% - 1px, which is 510px - 1px (remember, the container is 1020px wide in my example). That places it at 509px, or its center at 510px, which is right where the first cell and gap meet. The second divider is at 75% - 1px, which is 765px - 1px. That places its left edge at 764px and its center at 765px, which happens to fall just in the middle of the second gap.

Essentially, your calc() statements don’t take the details of grid layout into account, so one of them is off. If you turned the second divider around so its position was calculated from the right side (right: 25% - 1px) instead of the left, they’d both be off. I was able to make your layout work as intended by changing the first divider’s CSS to left: calc(50% - 6px), where the 6px is 1px plus half the gap width (times the number of gaps to its right, which is this case was 1).

What you most likely really want is the ability to just set borders on grid items, or else decorations in gaps, rather than having to calculate their positions yourself. These have been (are being) discussed, but as far as I know haven’t yet moved to the stage of being implemented.

Tenkir commented 2 years ago

I see. That still seems incorrect though (though it may be correct as per the spec).

If I set a grid to be 50fr, I would expect that it would take up 50% of the space of the grid (minus any evenly aligned gaps).

The expected behavior based on the developer documentation would result in the following: a grid of 1020px, with a gap size of 10px, the first column would result in a width of 505px.

The layout would be:

505px | 10px | 247.5px | 10px | 247.5px

Given that this is expected behavior, is it not well documented in any developer documentation (MDN or the like), and results in counter intuitive layout behavior.


Regardless, this is helpful in that I can at least implement a solution that will work as expected here.

Loirooriol commented 2 years ago

Gaps are just like additional tracks. So it's like you had 50fr 10px 25fr 10px 25fr.

The grid algorithm can't know that you actually want the 50fr to be as big as 25fr + 10px + 25fr in order to have the 1st gap centered at 50%.

If I set a grid to be 50fr, I would expect that it would take up 50% of the space of the grid (minus any evenly aligned gaps).

This is exactly what happens. 50fr is like (100% - 2*10px)/2, and 25fr is like (100% - 2*10px)/4. So the sum of 50fr + 25fr + 25fr is like 100% - 20px which is the total space minus the gaps.

You can use something like

grid-template-columns: calc(50% - 5px) calc(25% - 10px) calc(25% - 5px);

Then the 1st gap will be centered at 50%, and the 2nd gap at 75%, but the 2nd and 3rd tracks will have different sizes.

Alternatively, consider having an even number of tracks:

.app {
  grid-template-columns: repeat(4, 1fr);
  grid-template-areas: "a a b c";
}
.cell1 { grid-column: a; }
.cell2 { grid-column: b; }
.cell3 { grid-column: c; }

For 1020px, this will be like a:505px | gap:10px | b:247.5px | gap:10px | c:247.5px (but the last gap will not be at 75%).

meyerweb commented 2 years ago

@Loirooriol You meant grid-template-columns rather than -rows, right?

SebastianZ commented 2 years ago

What you most likely really want is the ability to just set borders on grid items, or else decorations in gaps, rather than having to calculate their positions yourself. These have been (are being) discussed, but as far as I know haven’t yet moved to the stage of being implemented.

For reference, styling gaps is being discussed in #2748 and #6748.

Sebastian

manuelmeister commented 10 months ago

Could this be solved, if we could define the columns like this?

column-gap: 10px;
grid-template-columns: calc(50fr + 10px) 25fr 25fr;

I also need to align a grid with a css columns, and it is hard