Working With Masonry Layouts
It appears that the CSS Working Group has decided to add masonry layouts as part of the [CSS Grid Level 3](https://drafts.csswg.org/css-grid-3/) specification. IMO this sucks but it appears to be a done deal that will hurt developers in the long run but, since it's a done deal it's time to move forward and learn how to work with it. Before we look at the details of how to work with the new masonry layout, I'll take a brief detour and explain why I think it's a bad idea. ## Why I think it's a bad idea In [Help us choose the final syntax for Masonry in CSS](https://webkit.org/blog/16026/css-masonry-syntax/), the WebKit team presents the following table comparing the option of just using grid with additional parameters and the masonry syntax proposed by the Chrome team in [An alternative proposal for CSS masonry](https://developer.chrome.com/blog/masonry) | Just Use Grid option | New Masonry Layout option | | :---: | :---: | | display: grid | display: masonry | | grid-template-columns /
grid-template-rows | masonry-template-tracks | | grid-template-rows: collapse /
grid-template-columns: collapse |masonry-direction: column /
masonry-direction: row | | grid-template-areas | masonry-template-areas | | grid-template | masonry-template | | grid-auto-flow | masonry-direction | | masonry-fill | masonry-flow | | gap | gap | | grid-column-start /
grid-row-start | masonry-track-start | | grid-column-end /
grid-row-end |masonry-track-end | | grid-column / grid-row | masonry-track | | grid-auto-columns / grid-auto-rows | masonry-auto-tracks | | grid | masonry | | grid-slack (name TBD) | masonry-slack (name TBD) | Then later in the article they state that: > It [the new layout] will require developers to memorize a parallel layout system with an entire second set of syntax. How is that different than having to learn masonry-specific values for existing grid properties? Does the number of properties to learn make a difference? Keeping masonry as part of grid adds an unnecessary cognitive switch of having to figure out what attributes work everywhere versus what works in Masonry or Grid alone. It'll be a matter of time to see how things work out. The feature is supported in Safari Technical Preview and in Firefox behind a flag. Once and if it's supported in Chromium we'll have another thing that we don't need third party libraries for and where we'll be able to benchmark performance betwee native CSS and third-party Javascript solutions. ## The specified CSS solution An example syntax for the new masonry layout makes a few changes to the grid layout syntax. The following example is taken from one of the [WebKit photos demo](https://webkit.org/demos/grid3/photos/). ```css main { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); grid-template-rows: masonry; gap: 1rem; } ``` The main difference is that we use `masonry` as the value for `grid-template-rows` instead of a specific value. This tells the browser to use the masonry layout for the rows. ## Houdini: The Layout Worklet The Houdini [Layout Worklet](https://drafts.css-houdini.org/css-layout-api/) is a way to define custom layout algorithms in Javascript. This is a powerful feature that allows developers to create custom layouts that can be used in CSS. The following is an example of a masonry layout implemented using the Layout Worklet. The first step is to define the layout worklet. This is best done in a separate file that we can import into our main script. ```js registerLayout('masonry', class { static get inputProperties() { return [ '--padding', '--columns' ]; } async intrinsicSizes() {} async layout(children, edges, constraints, styleMap) { const inlineSize = constraints.fixedInlineSize; const padding = parseInt(styleMap.get('--padding').toString()); const columnValue = styleMap.get('--columns').toString(); // We also accept 'auto', which will select the BEST number of columns. let columns = parseInt(columnValue); if (columnValue == 'auto' || !columns) { columns = Math.ceil(inlineSize / 350); // MAGIC NUMBER \o/. } // Layout all children with simply their column size. const childInlineSize = (inlineSize - ((columns + 1) * padding)) / columns; const childFragments = await Promise.all(children.map((child) => { return child.layoutNextFragment({fixedInlineSize: childInlineSize}); })); let autoBlockSize = 0; const columnOffsets = Array(columns).fill(0); for (let childFragment of childFragments) { // Select the column with the least amount of stuff in it. const min = columnOffsets.reduce((acc, val, idx) => { if (!acc || val < acc.val) { return {idx, val}; } return acc; }, {val: +Infinity, idx: -1}); childFragment.inlineOffset = padding + (childInlineSize + padding) * min.idx; childFragment.blockOffset = padding + min.val; columnOffsets[min.idx] = childFragment.blockOffset + childFragment.blockSize; autoBlockSize = Math.max(autoBlockSize, columnOffsets[min.idx] + padding); } return {autoBlockSize, childFragments}; } }); ``` Next, we need to load the woklet on the page. We first check if the CSS object supports the `layoutWorklet` property. If it does, we add the module to the layout worklet using the `addModule` property of the layoutWorklet object. If it doesn't, we display a message to the user. ```js if ('layoutWorklet' in CSS) { CSS.layoutWorklet.addModule('masonry.js'); } else { document.body.innerHTML = 'You need support for CSS Layout API to view this demo :('; } ``` Finally, we can use the masonry layout in our CSS using the `layout` function as the value for the `display` property with a single argument. The value of the argument is the full name of the worklet we defined in Javascript. ```css main { display: layout(masonry); --padding: 1rem; --columns: auto; } ``` ## Third-party Javascript Masonry has been a challenge for a long time. One of the libraries that has been around for a long time is [Masonry](https://masonry.desandro.com/). It's a large library and will add weight to your page but it's a good option if you need to support older browsers or if you need features that are not available in the CSS solution. The first part of the example is the HTML. The styling is done in CSS and will be controlled by the CSS classes attached to the children element. Also worth pointing out. The example's children are empty and sized with CSS. In production examples we could use text or images to affect the size of the children and the layout. ```html