desandro / masonry

:love_hotel: Cascading grid layout plugin
https://masonry.desandro.com
16.38k stars 2.11k forks source link

Stacked items with percentage or vw heights cause column gaps at certain grid widths #1064

Closed johnellmore closed 5 years ago

johnellmore commented 6 years ago

Test case: https://codepen.io/johnellmore/pen/gjBRve -- try 665px width in recent Chrome versions (v67)

I've reviewed several other GitHub issues, and I don't think this one is quite the same. It's probably related to #795.

If you have grid items which have high-precision heights, sometimes columns are skipped even when there's a gap. This appears to be due to a rounding error.

In the codepen example above, all items are based on a square grid with three columns. Some grid items are 1x1 squares and others are 1x2 rectangles. When a 1x2 rectangle is horizontally adjacent to two vertically-stacked 1x1 squares, the 1x2 rectangle is sometimes considered taller than the two 1x1's. This causes the next row to start underneath the 1x1's instead of underneath the 1x2.

Because this is a three-column square grid, the item heights are percentage- or viewport-width-based:

.height1 { height: 33.3333333vw; }
.height2 { height: 66.6666666vw; }

When viewing the codepen example at 665px width (or other affected widths), masonry calculates the height of the 1x2 rectangle as 443.328px and the height of the 1x1 squares as 221.656px. Notice that the 1x2 rectangle height isn't equal to the heights of 2 1x1's:

221.656 * 2 = 443.312
443.312 < 443.328

So the stack of squares is treated as shorter than the rectangle.

Changing from viewport units (vw) to percentage units doesn't fix the issue. (I used the nested element padding-top trick to maintain responsive aspect ratios)

My workaround: I was able to work around the issue in my case by dropping the precision of the 1x2 rectangle's height:

.height1 { height: 33.3333333vw; }
.height2 { height: 66.66vw; }

This causes masonry to calculate the 1x2's height as less than 1px shorter than the stacked squares, but it was close enough that there wasn't an ugly pixel gap. I basically kept chopping decimal places off of the 1x2 column height until I got a value that worked.

I'm not familiar enough with masonry's internals to suggest a solution here. But it definitely sounds like a rounding error of some sort.

Hope this is helpful!

desandro commented 6 years ago

Thanks for digging into this issue.

Yeah, this looks like a 1px CSS vs JS calculation discrepancy. If the horizontal ordering is a top priority, I recommend enabling horizontalOrder