|
| 1 | +# Masonry Layout |
| 2 | + |
| 3 | +## Problem description |
| 4 | + |
| 5 | +Masonry layout is a common Web design pattern where a number of items — commonly images or short article summaries — are placed one by one into columns, so-called because it resembles stone masonry. Unlike regular column layout, where items are placed vertically in the first column until they must spill over to the second column, masonry layout selects a column for each new item such that it is generally closer to the top of the layout than items placed later. |
| 6 | + |
| 7 | +The Pinterest search results page exemplifies this layout: |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +Here, each item has a different height (depending on the content and the width of the column), and inspecting the DOM reveals (as the visual content itself gives no indication of ordering) that each item has been placed into the column with the smallest height so far. |
| 12 | + |
| 13 | +An advantage to using this layout over regular column layout is that the masonry layout container height grows as more items are placed into it, as a balancing multi-column layout would, but the effect for the reader is that scrolling down will naturally lead to "later" items in the layout (that is, those less relevant in the search results). |
| 14 | + |
| 15 | +Achieving this layout without knowing upfront how tall each item will be is not possible without using script. |
| 16 | + |
| 17 | +## Examples demonstrating proposed solution |
| 18 | + |
| 19 | +Our proposed solution is an extension to the CSS Grid model to support the automatic placement of grid items using masonry rules instead of the existing auto-placement algorithm. Below are some examples to demonstrate. |
| 20 | + |
| 21 | +### Example 1 – Simple masonry layout |
| 22 | + |
| 23 | +This shows how to achieve the Pinterest search results page layout: |
| 24 | + |
| 25 | +```html |
| 26 | +<style> |
| 27 | +/* masonry layout with items automatically placed in as many 200px columns as will fit */ |
| 28 | +.results { |
| 29 | + display: grid; |
| 30 | + grid-template-rows: masonry; |
| 31 | + grid-template-columns: repeat(auto-fill, 200px); |
| 32 | + gap: 40px; |
| 33 | + justify-content: center; /* center columns in the grid container */ |
| 34 | +} |
| 35 | +
|
| 36 | +/* size images to take up their grid column width */ |
| 37 | +img { width: 100%; } |
| 38 | +</style> |
| 39 | +<div class="results"> |
| 40 | + <div><img src="R1.jpg"><br>❤️😃😮 42</div> |
| 41 | + <div><img src="R2.jpg"></div> |
| 42 | + <div><img src="R3.jpg"></div> |
| 43 | + <div><img src="R4.jpg"></div> |
| 44 | + ... |
| 45 | +</div> |
| 46 | +``` |
| 47 | + |
| 48 | + |
| 49 | + |
| 50 | +By specifying `grid-template-rows: masonry`, we cause items to be placed in the column that has the least content so far. Columns tracks are created as usual (due to the `repeat(auto-fill, 200px)` value for `grid-template-columns`), but row tracks are not used for the final layout of the grid items; instead each item assigned to a column is positioned just below (including any `gap`) the previous item. |
| 51 | + |
| 52 | +### Example 2 – Spanning columns |
| 53 | + |
| 54 | +Since this is a type of grid layout, we can make items span multiple columns. Taking the same Pinterest example and making all portrait orientation images span two columns: |
| 55 | + |
| 56 | +```html |
| 57 | +... |
| 58 | +<div class="results"> |
| 59 | + <div><img src="R1.jpg"><br>❤️😃😮 42</div> |
| 60 | + <div style="grid-column: auto / span 2;"><img src="R2.jpg"></div> |
| 61 | + <div><img src="R3.jpg"></div> |
| 62 | + <div><img src="R4.jpg"></div> |
| 63 | + ... |
| 64 | +</div> |
| 65 | +``` |
| 66 | + |
| 67 | + |
| 68 | + |
| 69 | +Here, the `grid-column: auto / span 2` property causes the second item to span two column tracks. `auto` is used for the starting grid line, allowing the automatic placement by the masonry layout algorithm into the next appropriate column to work. |
| 70 | + |
| 71 | +### Example 3 — Non-uniform column widths |
| 72 | + |
| 73 | +As with regular CSS Grid layout, columns can be assigned different widths: |
| 74 | + |
| 75 | +```html |
| 76 | +<style> |
| 77 | +.results { |
| 78 | + display: grid; |
| 79 | + grid-template-rows: masonry; |
| 80 | + grid-template-columns: 100px repeat(auto-fill, 200px) 100px; |
| 81 | + gap: 40px; |
| 82 | + justify-content: center; |
| 83 | +} |
| 84 | +img { width: 100%; } |
| 85 | +</style> |
| 86 | +<div class="results"> |
| 87 | + ... |
| 88 | +</div> |
| 89 | +``` |
| 90 | + |
| 91 | + |
| 92 | + |
| 93 | +### Example 4 – Definite placement of items |
| 94 | + |
| 95 | +If we want to ensure an item is placed in a specific column, we can use the regular grid placement properties. For example, to place the item with the reaction emoji in the final column: |
| 96 | + |
| 97 | +```html |
| 98 | +... |
| 99 | +<div class="results"> |
| 100 | + <div style="grid-column: -2;"><img src="R1.jpg"><br>❤️😃😮 42</div> |
| 101 | + <div><img src="R2.jpg"></div> |
| 102 | + <div><img src="R3.jpg"></div> |
| 103 | + <div><img src="R4.jpg"></div> |
| 104 | + ... |
| 105 | +</div> |
| 106 | +``` |
| 107 | + |
| 108 | + |
| 109 | + |
| 110 | +### Example 5 – Intrinsic size based column widths |
| 111 | + |
| 112 | +Tracks can be sized based on item intrinsic sizes, with some limitations due to masonry automatic placement occurring after track sizing: |
| 113 | + |
| 114 | +```html |
| 115 | +<style> |
| 116 | +body { font: 16px sans-serif; } |
| 117 | +h1 { font-size: 24px; text-transform: uppercase; margin-top: 0; } |
| 118 | +img { width: 100%; } |
| 119 | +
|
| 120 | +.container { |
| 121 | + display: grid; |
| 122 | + /* auto size the first column; only the first automatically placed item in this |
| 123 | + column's intrinsic size influences the track width */ |
| 124 | + grid-template-rows: masonry; |
| 125 | + grid-template-columns: auto repeat(3, 150px); |
| 126 | + gap: 20px; |
| 127 | + justify-content: center; |
| 128 | +} |
| 129 | +
|
| 130 | +.feature { |
| 131 | + background-color: #ddd; |
| 132 | + border-radius: 8px; |
| 133 | + padding: 8px; |
| 134 | + width: min-content; |
| 135 | +} |
| 136 | +</style> |
| 137 | +<div class="container"> |
| 138 | + <div class="feature"> |
| 139 | + <h1>Eyjafjallajökull</h1> |
| 140 | + <div>Icelandic volcano name causes havoc for page layouts across the Web!</div> |
| 141 | + </div> |
| 142 | + <div><img src="R1.jpg"></div> |
| 143 | + <div><img src="R2.jpg"></div> |
| 144 | + ... |
| 145 | +</div> |
| 146 | +``` |
| 147 | + |
| 148 | + |
| 149 | + |
| 150 | +### Example 6 – Aligning items in a column |
| 151 | + |
| 152 | +Since items within each column are not aligned with items in adjacent columns, there is an opportunity to align all of the items vertically in each column independently. (As opposed to regular grid layout, where only either tracks as a whole, or items within a single grid area, can be aligned.) |
| 153 | + |
| 154 | +```html |
| 155 | +<style> |
| 156 | +... |
| 157 | +.container { |
| 158 | + /* auto size the first column; only the first automatically placed item in this |
| 159 | + column's intrinsic size influences the track width */ |
| 160 | + grid-template-rows: masonry; |
| 161 | + grid-template-columns: auto repeat(3, 150px); |
| 162 | + gap: 20px; |
| 163 | + justify-content: center; |
| 164 | + |
| 165 | + /* align items in each column to the bottom of the container */ |
| 166 | + align-tracks: bottom; |
| 167 | +} |
| 168 | +... |
| 169 | +</style> |
| 170 | +... |
| 171 | +``` |
| 172 | +
|
| 173 | + |
| 174 | +
|
| 175 | +
|
| 176 | +## Summary of proposed solution |
| 177 | +
|
| 178 | +In the common case (and as shown in all the examples above), items are stacked in the grid columns, without the vertical alignment that comes from items being placed into rows as in regular CSS Grid layout. The horizontal axis (along which the vertical grid lines are placed) is termed the "grid axis", and the vertical axis (in which items are stacked) is termed the "masonry axis". At most one axis can use masonry layout. |
| 179 | +
|
| 180 | +Syntax: |
| 181 | +
|
| 182 | +* a new value `masonry` for `grid-template-columns` and `grid-template-rows` properties, indicating that masonry layout is to be used for the grid container in the specified axis |
| 183 | +* new properties `justify-tracks` and `align-tracks` (taking the same values as their `justify-content`/`align-content` cousins), which can be used to align items within a grid axis track |
| 184 | +* a new property `masonry-auto-flow`, to control aspects of the masonry layout algorithm |
| 185 | +
|
| 186 | +**[For ease of explanation, we assume below that masonry layout is specified for `grid-template-rows`.]** |
| 187 | +
|
| 188 | +Conceptually, a grid container using masonry layout only generates a one dimensional grid. No grid rows are generated. |
| 189 | +
|
| 190 | +Grid columns are generated as a result of the usual `grid-template-columns`, `grid-auto-columns`, and item placement properties. |
| 191 | +
|
| 192 | +The grid columns are sized by running the standard [grid item placement algorithm](https://drafts.csswg.org/css-grid/#auto-placement-algo) to generate a hypothetical grid, find a placement for each item in this hypothetical grid, and then running the [grid sizing algorithm](https://drafts.csswg.org/css-grid/#algo-overview) on it to compute the column sizes. Only the intrinsic sizes of the following items are taken into account when sizing the columns: |
| 193 | +
|
| 194 | +* items that have a definite placement in the grid axis (e.g. with `grid-column: 3 / span 2`) |
| 195 | +* items that have an automatic placement that puts them in the first row |
| 196 | +* items that span all columns of the grid |
| 197 | +
|
| 198 | +(Since masonry layout places items into columns depending on the filled height of the columns so far, the only items that we know will be in specific columns regardless of layout, and thus can be taken into account when sizing the columns, are these ones.) |
| 199 | +
|
| 200 | +Once the grid columns have been created and sized, masonry layout is performed to place each item. Each grid column keeps track of a running position, initialized to zero, which is used to determine which column (or span of columns) to assign the next item to. As each subsequent item is placed, the running position for the column (or span of columns) the item is placed in is updated. `gap` values and margins between items are applied. |
| 201 | +
|
| 202 | +The masonry grid item placement is done in `order`-modified document order. |
| 203 | +
|
| 204 | +The `masonry-auto-flow` property allows control of aspects of the masonry layout algorithm. Its initial value is `definite-first pack`, which causes definitely placed items to be handled before automatically placed items (since that is how the regular grid layout placement algorithm works), and for the columns chosen for each item to be based on the smallest running position so far. Keywords to override these defaults: |
| 205 | +
|
| 206 | +* an `ordered` value in place of `definite-first`, which causes items to be placed in their pure `order`-modified document order, rather than selecting definitely placed items first |
| 207 | +* a `next` value in place of `pack`, which causes each column in term to be selected, rather than using the running position to choose the shortest one |
| 208 | +
|
| 209 | +Finally, we use the value of `align-tracks` to align, distribute, and stretch the items in each column. This supports a comma separated list of keywords to apply to each grid column, with the last value applying to any remaining columns. Alignment in the grid axis behaves as in regular grid layout. |
| 210 | +
|
| 211 | +## Details |
| 212 | +
|
| 213 | +More details, including on sizing the masonry grid container, performing baseline alignment of items, fragmentation, and interaction with subgrid, are in [#4650](https://github.com/w3c/csswg-drafts/issues/4650). |
| 214 | +
|
| 215 | +## Prototype |
| 216 | +
|
| 217 | +A prototype of this proposal is available in Firefox Nightly, behind a pref. See [this comment](https://github.com/w3c/csswg-drafts/issues/4650#issuecomment-620864503) for how to enable it. |
| 218 | +
|
| 219 | +## Issues |
| 220 | +
|
| 221 | +* How do we define a static position for absolutely positioned children of a masonry grid container, and what containing block do we use to align them? |
| 222 | +
|
| 223 | +## Why extend CSS Grid? |
| 224 | +
|
| 225 | +The reasons we think extending CSS Grid is a reasonable solution, as opposed to creating a wholly new layout model with a new `display` type: |
| 226 | +
|
| 227 | +* It suits requirements from existing masonry layouts found on the Web, such as having items that span multiple columns and using gaps between items. |
| 228 | +* It avoids needing to duplicate concepts from CSS Grid, which means that it should not only be easier to specify, but it will also be easier for authors to learn if they are already familiar with Grid and Alignment. |
| 229 | +* It supports more control than a solution written from scratch likely would, e.g. by getting CSS Grid line name resolution, item placement, complex track sizing functions, and subgrid support for free. |
| 230 | +* Supporting masonry layout in developer tools will require much less work, since it's easy to augment existing Grid tools. |
| 231 | +* It allows for graceful degradation to a regular grid layout, if written with for example: |
| 232 | +
|
| 233 | +```css |
| 234 | +grid-template-rows: masonry; /* ignored if masonry not supported */ |
| 235 | +grid-template-columns: repeat(4, 100px); /* applied even if masonry not supported */ |
| 236 | +``` |
| 237 | +
|
| 238 | +## Alternative solutions |
| 239 | +
|
| 240 | +If the script overhead is acceptable, then there are a number of script libraries and framework plugins that implement masonry layout, the first of which was [Masonry by David DeSandro](https://masonry.desandro.com/). As with all widely deployed scripted layout solutions, they can only participate in the surrounding page layout in limited, less efficient ways than built-in engine support for a layout model. Although often this is sufficient. |
| 241 | +
|
| 242 | +Houdini Layout provides a script API for authors to correctly hook into the browser's layout engine, so this provides a way to correctly participate in the surrounding page layout. This comes at the cost of being more complex to write than a simple, resize event or `ResizeObserver` using script. |
| 243 | +
|
0 commit comments