仅限 CSS 的砌体布局

CSS-only masonry layout

提问人:Pekka 提问时间:6/6/2017 最后编辑:Temani AfifPekka 更新时间:4/19/2023 访问量:72069

问:

我需要实现砌体布局。但是,出于多种原因,我不想使用 JavaScript 来做到这一点。

A grid of multiple columns of rectangles of varying height.

参数:

  • 所有元素的宽度都相同
  • 元素的高度无法在服务器端计算(图像加上各种数量的文本)
  • 如果有必要,我可以忍受固定数量的列

有一个适用于现代浏览器的简单解决方案,即 column-count 属性。

该解决方案的问题在于元素按列排序:

Starting from the top leftmost box, they're numbered 1 through 4 straight down, the topmost box in the next column is 5, and so on.

虽然我需要将元素按行排序,但至少大致如下:

Starting from the top leftmost box, they're numbered 1 through 6 straight across, but because box 5 is the shortest the box underneath it is 7 as it has the appearance of being on a row higher than the next box on the far left.

我尝试过但行不通的方法:

现在我可以更改服务器端呈现并重新排序项目,将项目数除以列数,但这很复杂,容易出错(基于浏览器决定将项目列表拆分为列的方式),所以如果可能的话,我想避免它。

有没有一些 flexbox 魔力使这成为可能?

HTML 弹性框 CSS-网格

评论

8赞 Gabriele Petrioli 6/6/2017
想不出一种不依赖于预定义高度的方法。如果你重新考虑JS,看看 stackoverflow.com/questions/13518653/......我在那里实现了这样一个非常简单的解决方案。
4赞 I haz kode 7/24/2017
我意识到你说的是仅限CSS。我只想提一下,Masonry 不再需要 jQuery——缩小后的库不到 8kb——并且可以单独使用 html 进行初始化。仅供参考 jsfiddle.net/wp7kuk1t
1赞 Timothy Zorn 7/25/2017
如果你能提前确定元素的高度,通过知道行高、字体大小(你必须提供特定的字体并做一些巧妙的计算)、图像高度、垂直边缘和填充,你就可以做到这一点。否则,您不能仅使用 CSS 执行此操作。您还可以使用 PhantomJS 之类的东西来预渲染每个元素并获取该元素的高度,但会增加大量开销/延迟。
0赞 Flimtix 3/31/2022
几乎所有可能的砌体布局都可以在这里找到。请注意,还有 js 解决方案。

答:

263赞 Michael Benjamin 7/20/2017 #1

2021年更新

CSS 网格布局级别 3 包括一项功能。masonry

代码将如下所示:

grid-template-rows: masonry
grid-template-columns: masonry

截至 2021 年 3 月,它仅在 Firefox 中可用(激活标志后)。

结束更新;原文答案如下


弹性盒

使用 flexbox 无法实现动态砌体布局,至少不能以干净有效的方式进行。

Flexbox 是一个一维布局系统。这意味着它可以沿水平线或垂直线对齐项目。Flex 项仅限于其行或列。

真正的网格系统是二维的,这意味着它可以沿着水平线和垂直线对齐项目。内容项可以同时跨行和跨列,这是 Flex 项无法做到的。

这就是为什么 flexbox 构建网格的能力有限的原因。这也是 W3C 开发另一种 CSS3 技术 Grid Layout 的原因。


row wrap

在带有 的 flex 容器中,flex 项必须换行到新flex-flow: row wrap

这意味着一个 flex 项目不能换行在同一行中的另一个项目下

请注意上面的 div #3 如何换行在 div #1 下面,从而创建一个新行。它不能换行在 div #2 下面。

因此,当项目不是行中最高的时,留白会保留,从而产生难看的间隙。


column wrap

如果切换到 ,则更容易实现类似网格的布局。但是,列方向容器立即存在四个潜在问题:flex-flow: column wrap

  1. Flex 项目是垂直流动的,而不是水平流动的(就像您在本例中需要的那样)。
  2. 容器是水平扩展的,而不是垂直扩展的(如 Pinterest 布局)。
  3. 它要求容器具有固定的高度,以便物品知道在哪里包装。
  4. 在撰写本文时,它在所有主要浏览器中都存在缺陷,其中容器无法扩展以容纳其他列

因此,在这种情况下,列方向容器不是一个选项,在许多其他情况下也是如此。


未定义项目维度的 CSS 网格

如果可以预先确定内容项的各种高度,网格布局将是解决您问题的完美解决方案。所有其他要求都在 Grid 的能力范围内。

必须知道网格项的宽度和高度,以便缩小与周围项的间隙。

因此,Grid 是构建水平流动砌体布局的最佳 CSS,在这种情况下却不足。

事实上,在CSS技术出现并能够自动缩小差距之前,CSS通常没有解决方案。像这样的事情可能需要重排文档,所以我不确定它会有多大用处或效率。

你需要一个脚本。

JavaScript 解决方案倾向于使用绝对定位,即从文档流中删除内容项,以便重新排列它们,没有间隙。下面是两个示例:

Masonry 是一个 JavaScript 网格布局库。它 工作原理是将元素放置在可用的最佳位置 垂直空间,有点像泥瓦匠在墙上安装石头。

来源: http://masonry.desandro.com/

[Pinterest] 确实是一个很酷的网站,但我觉得有趣的是这些钉板是如何布置的......因此,本教程的目的是我们自己重新创建这种响应式块效果......

来源: https://benholland.me/javascript/2012/02/20/how-to-build-a-site-that-works-like-pinterest.html


定义了项目维度的 CSS 网格

对于已知内容项的宽度和高度的布局,下面是纯 CSS 中水平流动的砌体布局:

grid-container {
  display: grid;                                                /* 1 */
  grid-auto-rows: 50px;                                         /* 2 */
  grid-gap: 10px;                                               /* 3 */
  grid-template-columns: repeat(auto-fill, minmax(30%, 1fr));   /* 4 */
}

[short] {
  grid-row: span 1;                                             /* 5 */
  background-color: green;
}

[tall] {
  grid-row: span 2;
  background-color: crimson;
}

[taller] {
  grid-row: span 3;
  background-color: blue;
}

[tallest] {
  grid-row: span 4;
  background-color: gray;
}

grid-item {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3em;
  font-weight: bold;
  color: white;
}
<grid-container>
  <grid-item short>01</grid-item>
  <grid-item short>02</grid-item>
  <grid-item tall>03</grid-item>
  <grid-item tall>04</grid-item>
  <grid-item short>05</grid-item>
  <grid-item taller>06</grid-item>
  <grid-item short>07</grid-item>
  <grid-item tallest>08</grid-item>
  <grid-item tall>09</grid-item>
  <grid-item short>10</grid-item>
  <grid-item tallest>etc.</grid-item>
  <grid-item tall></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item tallest></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item tallest></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
</grid-container>

jsFiddle 演示


运作方式

  1. 建立块级网格容器。(将是另一种选择)inline-grid

  2. grid-auto-rows 属性设置自动生成的行的高度。在此网格中,每行的高度为 50px。

  3. grid-gap 属性是 和 的简写。此规则在网格项之间设置 10px 的间距。(它不适用于项目和容器之间的区域。grid-column-gapgrid-row-gap

  4. grid-template-columns 属性设置显式定义列的宽度。

    重复表示法定义了重复列(或行)的模式。

    自动填充功能告诉网格在不溢出容器的情况下排列尽可能多的列(或行)。(这可以创建与 flex 布局类似的行为。flex-wrap: wrap

    minmax() 函数为每列(或行)设置最小和最大大小范围。在上面的代码中,每列的宽度最小为容器的 30%,最大为任何可用空间。

    fr 单位表示网格容器中可用空间的一小部分。它与 flexbox 的属性相当。flex-grow

  5. 使用 grid-rowspan,我们告诉网格项它们应该跨越多少行。

评论

8赞 Oliver Joseph Ash 1/4/2018
奇妙的答案!使用“定义了项目维度的 CSS 网格”解决方案,是否可以将单元格的高度表示为单元格宽度的百分比?这对于显示纵横比已知的图像很有用。我们希望始终保持纵横比。
0赞 Michael Benjamin 1/4/2018
谢谢。关于图像的纵横比问题,“单元格宽度百分比”方法(百分比填充技巧?),在 Grid 或 Flexbox 中并不可靠。请参阅此处和此处@OliverJosephAsh
0赞 Oliver Joseph Ash 1/4/2018
是的,我指的是百分比填充技巧。是否可以将任何解决方法与您的砌体布局解决方案结合使用?对于上下文,我们希望为 unsplash.com 上的图像实现仅CSS砌体布局。
1赞 Oliver Joseph Ash 1/4/2018
不幸的是,为此使用 JS 不是一种选择。出于性能和 SEO 原因,我们希望启用服务器端渲染。这意味着必须在客户端 JavaScript 下载、解析和执行之前呈现布局。鉴于这似乎是不可能的,我想我们将不得不在某个地方做出权衡!感谢您的帮助:-)
2赞 Michael Benjamin 5/11/2018
规格更新:在 flexbox 中,现在设置了百分比边距和填充,以再次解析容器的内联大小。drafts.csswg.org/css-flexbox/#item-margins@OliverJosephAsh
25赞 Oliver Joseph Ash 6/16/2019 #2

这是最近发现的涉及 flexbox 的技术:https://tobiasahlin.com/blog/masonry-with-css/

这篇文章对我来说很有意义,但我没有尝试使用它,所以我不知道除了迈克尔的回答中提到的之外,是否有任何警告。

下面是文章中的一个示例,利用该属性,结合 .order:nth-child

堆栈代码段

.container {
  display: flex;
  flex-flow: column wrap;
  align-content: space-between;
  /* Your container needs a fixed height, and it 
   * needs to be taller than your tallest column. */
  height: 960px;
  
  /* Optional */
  background-color: #f7f7f7;
  border-radius: 3px;
  padding: 20px;
  width: 60%;
  margin: 40px auto;
  counter-reset: items;
}

.item {
  width: 24%;
  /* Optional */
  position: relative;
  margin-bottom: 2%;
  border-radius: 3px;
  background-color: #a1cbfa;
  border: 1px solid #4290e2;
  box-shadow: 0 2px 2px rgba(0,90,250,0.05),
    0 4px 4px rgba(0,90,250,0.05),
    0 8px 8px rgba(0,90,250,0.05),
    0 16px 16px rgba(0,90,250,0.05);
  color: #fff;
  padding: 15px;
  box-sizing: border-box;
}

 /* Just to print out numbers */
div.item::before {
  counter-increment: items;
  content: counter(items);
}

/* Re-order items into 3 rows */
.item:nth-of-type(4n+1) { order: 1; }
.item:nth-of-type(4n+2) { order: 2; }
.item:nth-of-type(4n+3) { order: 3; }
.item:nth-of-type(4n)   { order: 4; }

/* Force new columns */
.break {
  flex-basis: 100%;
  width: 0;
  border: 1px solid #ddd;
  margin: 0;
  content: "";
  padding: 0;
}

body { font-family: sans-serif; }
h3 { text-align: center; }
<div class="container">
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 190px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 120px"></div>
  <div class="item" style="height: 160px"></div>
  <div class="item" style="height: 180px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 150px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 190px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 120px"></div>
  <div class="item" style="height: 160px"></div>
  <div class="item" style="height: 180px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 150px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 170px"></div>
  
  <span class="item break"></span>
  <span class="item break"></span>
  <span class="item break"></span>
</div>

评论

1赞 Asons 6/16/2019
首先,只有链接的答案是坏的,因为当这样的链接死亡时,答案也会消失。其次,如果你更仔细地阅读给定的答案,你会发现你的链接中提到的内容被覆盖了。简而言之,单独使用 Flexbox 有太多限制,除非上面提到的不是问题。
4赞 Oliver Joseph Ash 6/16/2019
我已经非常彻底地阅读了公认的答案——你甚至会看到我在它底部添加的一些评论。我链接到的 flexbox 方法是独一无二的,并且不包含在该答案中。我不想重复这篇文章,因为它非常复杂,而这篇文章在这方面做得很好。
1赞 Asons 6/16/2019
链接的唯一不同之处在于利用该属性,使其看起来像项目从左向右流动。尽管如此,它的主要诀窍是,这在上面的答案中有所提及。此外,它不能像真正的砌体那样流动项目,例如,如果第 3 和第 4 个项目适合第 3 列,它们会,而链接的项目则不适合。但正如我所说,这完全取决于要求是什么,在这种情况下(这个问题),它不会按照要求工作。orderflex-flow: column wrap
2赞 Asons 6/16/2019
此外,它不是关于“你不想重复这篇文章”,答案应该包含代码的基本部分,所以当链接的资源死亡时它仍然有效。
1赞 Oliver Joseph Ash 6/16/2019
公平点。我认为无论如何在这里分享这篇文章会很有用,因为它建立在前面提到的其他技术之上,虽然它没有回答最初的问题,但它可能会帮助其他可以接受这些限制的人。我很高兴删除我的答案并将链接作为评论重新分享,并指出关键区别()。order
4赞 Temani Afif 3/3/2021 #3

最后,一个仅CSS的解决方案,可以轻松创建砌体布局,但我们需要耐心等待,因为目前不支持它。

此功能是在 CSS 网格布局模块级别 3 中引入的

本模块介绍了砌体布局作为 CSS 网格容器的附加布局模式。

然后

网格容器通过为其其中一个轴指定值 masonry 来支持砌体布局。该轴称为砌体轴,另一轴称为网格轴。

一个基本的例子是:

.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-template-rows: masonry; /* this will do the magic */
  grid-gap: 10px;
}

img {
  width: 100%;
}
<div class="container">
  <img src="https://picsum.photos/id/1/200/300">
  <img src="https://picsum.photos/id/17/200/400">
  <img src="https://picsum.photos/id/18/200/100">
  <img src="https://picsum.photos/id/107/200/200">
  <img src="https://picsum.photos/id/1069/200/600">
  <img src="https://picsum.photos/id/12/200/200">
  <img src="https://picsum.photos/id/130/200/100">
  <img src="https://picsum.photos/id/203/200/100">
  <img src="https://picsum.photos/id/109/200/200">
  <img src="https://picsum.photos/id/11/200/100">
</div>

如果您启用该功能,这将在Firefox上产生以下结果,如下所示: https://caniuse.com/?search=masonry

  1. 打开 Firefox 并在 url 栏中写下 about:config
  2. 使用砌体进行搜索
  3. 你会得到一面旗帜,让它成为现实

CSS masonry layout

如果我们减少屏幕屏幕,响应部分是完美的!

Masonry layout using CSS only

评论

0赞 Stefan Mares 11/26/2023
它似乎在您的代码段中不起作用
5赞 نور 12/7/2021 #4

我发现这个解决方案,它很可能与所有浏览器兼容。

注意:如果有人发现任何错误或浏览器支持问题。请更新此答案或评论

CSS Profpery 支持参考:

列计数、间隙、填充内部分隔符内部分页符

注意:此属性已替换为该属性。page-break-insidebreak-inside

  

.container {
  -moz-column-count: 1;
       column-count: 1;
  -moz-column-gap: 20px;
       column-gap: 20px;
  -moz-column-fill: balance;
       column-fill: balance;
  margin: 20px auto 0;
  padding: 2rem;
}
.container .item {
  display: inline-block;
  margin: 0 0 20px;
  page-break-inside: avoid;
  -moz-column-break-inside: avoid;
       break-inside: avoid;
  width: 100%;
}
.container .item img {
  width: 100%;
  height: auto;
}
@media (min-width: 600px) {
  .container {
    -moz-column-count: 2;
         column-count: 2;
  }
}
@media (min-width: 900px) {
  .container {
    -moz-column-count: 3;
         column-count: 3;
  }
}
@media (min-width: 1200px) {
  .container {
    -moz-column-count: 4;
         column-count: 4;
  }
}
   
 CSS-Only Masonry Layout 
  
<div class="container">
  <div class="item"><img src="https://placeimg.com/600/400/animals" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/600/arch" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/300/nature" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/450/people" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/350/tech" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/800/animals/grayscale" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/650/arch/sepia" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/300/nature/grayscale" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/400/people/sepia" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/600/tech/grayscale" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/200/animals/sepia" alt=""></div>
  <div class="item"><img src="https://placeimg.com/600/700/arch/grayscale" alt=""></div>
</div>

评论

2赞 Saifur Rahman Mohsin 12/16/2022
是的,但如果你读了这个问题,目标是逐行实现这一目标。OP 已经知道按列解决方案,这很简单。
0赞 mplungjan 1/25/2023
也许有一些解释和兼容性评论
0赞 نور 1/25/2023
@mplungjan所有浏览器都支持它。column-count、gap、fill break-inside、page-break-inside 注意:此属性已替换为 break-inside 属性。
0赞 mplungjan 1/25/2023
因此,请使用此信息更新您的答案
3赞 GorvGoyl 4/19/2023 #5

这是使用 CSS 的响应方式。
注意:元素是按列排序的,而不是按行排序的。
columns

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>Responsive Grid Layout</title>
</head>
<body>
    <div class="wrapper">
        <div class="box box1">Content of box 1</div>
        <div class="box box2">Content of box 2</div>
        <div class="box box3">Content of box 3</div>
        <div class="box box4">Content of box 4</div>
        <div class="box box5">Content of box 5</div>
        <div class="box box6">Content of box 6</div>
        <div class="box box7">Content of box 7</div>
    </div>
</body>
</html>
body {
    margin: 0;
    display: flex;
    justify-content: center;
}

.wrapper {
  column-count: auto;
  column-width: 320px; /* 300px + 20px gap */
  column-gap: 20px;
  width: 100%;
  max-width: 1000px;
  padding: 20px;
  box-sizing: border-box;
}

.box {
  background-color: lightblue;
  border: 1px solid blue;
  padding: 20px;
  font-size: 1.5em;
  text-align: center;
  width: 300px;
  box-sizing: border-box;
  display: inline-block;
  margin-bottom: 20px;
  break-inside: avoid-column;
}

.box1 {
  height: 800px;
}
.box2 {
  height: 300px;
}
.box3 {
  height: 300px;
}
.box4 {
  height: 300px;
}

enter image description here