Shrnutí
Masonry layout — ten Pinterest-style mřížkový rozpad, kde každá karta má jinou výšku a sloupce se zaplňují podle nejkratšího — byl roky frustrace. Buď jsi tahal 30 kB JavaScriptu, nebo jsi sáhl po column-count a smířil se s tím, že čtenář čte shora dolů, ne zleva doprava. CSS Grid Lanes (display: grid-lanes) to konečně řeší přímo v CSS, bez JS, bez resize listenerů a s dvouřádkovým fallbackem přes @supports. Tenhle článek ti ukáže syntaxi, jak to nasadit dneska a kde je to past.
Proč mě to vůbec zajímá
Za posledních deset let jsem masonry řešil snad pětkrát. Pokaždé to dopadlo stejně: Masonry.js, Isotope, později pár ručních requestAnimationFrame skriptů. Každá implementace měla stejný neduh — než se obrázky doloadily, layout poskočil. ResizeObserver pomohl, ale ne úplně. A když pak design system přibalil i SSR, vesměs to skončilo skeleton placeholderama, abychom CLS udrželi pod 0.1.
CSS columns vypadalo jako záchrana, ale není. column-count: 3 zaplní sloupec shora dolů, takže pořadí čtení je: celý první sloupec → celý druhý → celý třetí. Pro chronologickou galerii (Pinterest, blog grid) je to absurdní. Uživatel skenuje řádek, my mu servírujeme sloupec.
Specifikace masonry v CSS se táhne snad od roku 2020. Safari Technology Preview začal s grid-template-rows: masonry a Chromium tým proti tomu podal námitku — že to spojuje dva nezávislé layout módy do jedné property. Pár let se hádali a výsledkem je display: grid-lanes: vlastní display value, čisté oddělení od běžného grid, žádná sémantická skrytá past.
Jak to funguje
Základní syntaxe je tak triviální, že ji odrecituješ ze spánku:
.container {
display: grid;
display: grid-lanes; /* podporováno-li, přetíží řádek nad */
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}
Tři věci, které stojí za vysvětlení:
Dvojité display: je progressive enhancement zadarmo. Prohlížeč, co grid-lanes nezná, druhý řádek prostě ignoruje a zůstane u grid. Žádné @supports, žádný JS. Nový prohlížeč přepíše hodnotu nahoře a má masonry. Tohle je pro mě nejhezčí věc na celé feature — nepotřebuješ vědět, kdo má jakou verzi.
Lanes = sloupce. Termín "lane" je jen jiné slovo pro to, co znáš jako track v běžném gridu. grid-template-columns definuje, kolik jich bude a jak jsou široké. Tady repeat(auto-fill, minmax(250px, 1fr)) říká: "vejde-li se 250px sloupec, nasázej jich kolik můžeš, a roztáhni je rovnoměrně".
Algoritmus zaplňování. Prohlížeč jde v DOM order položku po položce a každou cpe do nejkratší aktuální lane. Žádné výjimky, žádné optimalizace globální výšky — díky tomu je layout deterministický. Pořadí čtení (DOM) zůstane to, co jsi napsal: prvek N je vždycky před N+1.
Feature detection a fallback
I s dvojitým display: občas chceš pro starší prohlížeče nasadit jiný layout (typicky CSS columns nebo klasický grid). Na to je @supports:
@supports (display: grid-lanes) {
.gallery {
display: grid-lanes;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}
}
@supports not (display: grid-lanes) {
.gallery {
/* fallback — CSS columns nebo klasický grid */
column-count: 3;
column-gap: 16px;
}
.gallery > * {
break-inside: avoid;
margin-bottom: 16px;
}
}
Co tady stojí za pozornost: break-inside: avoid na potomky. Bez něj ti CSS columns roztrhnou kartu na dva sloupce, a to je přesně ten scénář, který bys nikdy nechtěl ukázat klientovi. Plus margin-bottom místo gap — column-gap neřídí svislé mezery uvnitř sloupce.
Pro klienty, co vyžadují IE11 nebo desetiletý Android browser (existují, věřte mi), je tohle absolutní strop. Nic dalšího pro ně dělat nebudu.
Pinterest galerie — kompletní příklad
<ul class="gallery">
<li><img src="/img/1.jpg" alt="" /></li>
<li><img src="/img/2.jpg" alt="" /></li>
<li><img src="/img/3.jpg" alt="" /></li>
<li><img src="/img/4.jpg" alt="" /></li>
<li><img src="/img/5.jpg" alt="" /></li>
<!-- … -->
</ul>
.gallery {
display: grid;
display: grid-lanes;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
list-style: none;
padding: 0;
margin: 0;
}
.gallery img {
display: block;
width: 100%;
height: auto;
border-radius: 8px;
}
To je celé. Žádný JS, žádný ResizeObserver, žádné poskoky při loadu. Obrázek si určí výšku sám podle aspect ratio a grid-lanes ho narve do nejkratší lane. Když přidáš aspect-ratio přímo na <img>, eliminuješ i CLS před stažením obrázků.
Kdy to nepoužít
Masonry je nástroj na vizuální zaplnění prostoru, ne na strukturovaná data. Tři scénáře, kdy se mu vyhni:
- Tabulky a dashboardy s metrikami. Pokud potřebuješ, aby řádky byly opravdu řádky (porovnání čísel mezi kartami), masonry ti zarovnání rozhází. Použij klasický grid s
grid-auto-rows: 1fr. - Formuláře. Vyplňování má pevný vertikální tok. Masonry tam nedává žádný smysl.
- Cokoliv s drag & drop reorderingem. Algoritmus je deterministický, ale pozice prvku se mění podle obsahu sousedů. Reorder UI to dělá nepředvídatelným.
Pravidlo: pokud zalomený řádek (row) nese sémantiku, masonry ne. Galerie, blog grid, dashboard widgetů s nezávislým obsahem — ano. Cokoliv jiného — radši ne.
Závěr
Masonry v CSS je jedna z těch změn, kterým fandím tiše dlouho. Ne kvůli tomu, že by zachraňovala produkty — Masonry.js fungoval. Ale je to jeden balíček navíc, jeden render hook, jeden zdroj poskoků. Tahle feature ho prostě smaže z bundle, a já dostanu zpátky pár kilobajt a hlavně klid v hlavě, že layout počítá engine, ne můj kód.
Pokud děláš galerii, blog grid nebo card layout s nestejnou výškou, nasadit display: grid-lanes s @supports fallbackem je teď otázka pěti minut. A ten fallback bude potřeba ještě tak rok, dva.
Jednou věcí míň, kvůli které tahat JavaScript.