纯 CSS 砌体布局

Posted

技术标签:

【中文标题】纯 CSS 砌体布局【英文标题】:CSS-only masonry layout 【发布时间】:2019-10-09 04:45:09 【问题描述】:

我需要实现砌体布局。但是,出于多种原因,我不想使用 javascript 来做这件事。

参数:

所有元素的宽度相同 元素的高度无法在服务器端计算(图像加上不同数量的文本) 如果需要,我可以使用固定数量的列

有一个适用于现代浏览器的简单解决方案,the column-count property.

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

虽然我需要将元素按行排序,但至少大约是:

我尝试过但不起作用的方法:

制作物品display: inline-block:wastes vertical space. 制作物品float: left:lol, no.

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

是否有一些 flexbox 魔法使这成为可能?

【问题讨论】:

想不出不依赖于预定义高度的方法。如果您重新考虑 JS,请查看 ***.com/questions/13518653/…,我在其中实现了这样一个非常简单的解决方案。 我知道你说的是 CSS-only。我只想提一下 Masonry 不再需要 jQuery - 缩小的库 低于 8kb - 并且可以单独使用 html 进行初始化。仅供参考jsfiddle.net/wp7kuk1t 如果您可以提前确定元素的高度,通过知道行高、字体大小(您必须提供特定字体并进行一些巧妙的计算)、图像高度, 垂直边距和内边距,你可以这样做。否则,您不能仅使用 CSS 来执行此操作。您还可以使用 PhantomJS 之类的东西来预渲染每个元素并获取该元素的高度,但是会增加大量开销/延迟。 【参考方案1】:

2021 年更新

CSS Grid Layout Level 3 包含masonry 功能。

代码将如下所示:

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

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

https://drafts.csswg.org/css-grid-3/#masonry-layout https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Masonry_Layout

结束更新;下面的原始答案


弹性盒

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

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

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

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


row wrap

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

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

注意上面的 div #3 如何包裹在 div #1 之下,创建一个新行。它不能包裹在 div #2 之下。

因此,当项目不是该行中最高的项目时,会留下空白,造成难看的间隙。


column wrap

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

    弹性项目垂直流动,而不是水平流动(就像您在这种情况下需要的那样)。 容器水平扩展,而不是垂直扩展(如 Pinterest 布局)。 It requires the container to have a fixed height, so the items know where to wrap. 在撰写本文时,它在 the container doesn't expand to accommodate additional columns 的所有主要浏览器中都存在缺陷。

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


CSS 网格项目尺寸未定义

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

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

因此,在这种情况下,网格,它是最好的 CSS 提供的用于构建水平流动的砌体布局的工具。

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

你需要一个脚本。

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

Desandro Masonry

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

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

How to Build a Site that Works Like Pinterest

[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 demo


工作原理

    Establish a block-level grid container.(inline-grid 是另一种选择) grid-auto-rows 属性设置自动生成的行的高度。在此网格中,每行的高度为 50 像素。 grid-gap 属性是grid-column-gapgrid-row-gap 的简写。此规则在网格项目之间设置 10px 的间隙。 (不适用于物品和容器之间的区域。) grid-template-columns 属性设置明确定义的列的宽度。

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

auto-fill 函数告诉网格在不溢出容器的情况下排列尽可能多的列(或行)。 (这可以创建类似于 flex 布局的 flex-wrap: wrap 的行为。)

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

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

    通过grid-rowspan,我们告诉网格项它们应该跨越多少行。

浏览器支持 CSS 网格

Chrome - 自 2017 年 3 月 8 日起提供全面支持(版本 57) Firefox - 自 2017 年 3 月 6 日起提供全面支持(版本 52) Safari - 自 2017 年 3 月 26 日起全面支持(版本 10.1) Edge - 自 2017 年 10 月 16 日起提供全面支持(版本 16) IE11 - 不支持当前规范;支持过时版本

这是完整的图片:http://caniuse.com/#search=grid


Firefox 中酷炫的网格叠加功能

在 Firefox 开发工具中,当您检查网格容器时,CSS 声明中有一个小网格图标。单击它会在页面上显示网格的轮廓。

更多详情:https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Examine_grid_layouts

【讨论】:

绝妙的答案!使用“定义了项目尺寸的 CSS 网格”解决方案,是否可以将单元格的高度表示为单元格宽度的百分比?这对于显示宽高比已知的图像很有用。我们希望始终保持纵横比。 谢谢。关于图像的纵横比问题,“单元格宽度百分比”方法(百分比填充技巧?),就此而言,它在 Grid 或 Flexbox 中不可靠。请参阅 herehere。 @OliverJosephAsh 不幸的是,为此使用 JS 不是一种选择。出于性能和 SEO 的原因,我们希望启用服务器端渲染。这意味着必须在客户端 JavaScript 下载、解析和执行之前呈现布局。鉴于这似乎是不可能的,我想我们将不得不在某个地方进行权衡!感谢您的帮助:-) 规范更新: 在 flexbox 中,现在设置百分比边距和填充以再次解析容器的内联大小。 drafts.csswg.org/css-flexbox/#item-margins@OliverJosephAsh 我已经添加了关于新功能的答案:***.com/a/66457526/8620333 :)【参考方案2】:

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

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

这是文章中的一个示例,它使用了order 属性,并结合了:nth-child

堆栈sn-p

.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>

【讨论】:

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

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

这个功能是在CSS Grid Layout Module Level 3中引入的

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

然后

网格容器支持砌体布局,方法是为其中一个轴指定值 ma​​sonry。这条轴线称为砌体轴线,另一轴线称为网格轴线。

一个基本的例子是:

.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

    打开 Firefox 并在 url 栏中写 about:config 使用砖石进行搜索 你会得到一个标志,让它成为现实

如果我们把屏幕屏幕缩小,响应式部分就完美了!

【讨论】:

【参考方案4】:

  

.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" ></div>
  <div class="item"><img src="https://placeimg.com/600/600/arch" ></div>
  <div class="item"><img src="https://placeimg.com/600/300/nature" ></div>
  <div class="item"><img src="https://placeimg.com/600/450/people" ></div>
  <div class="item"><img src="https://placeimg.com/600/350/tech" ></div>
  <div class="item"><img src="https://placeimg.com/600/800/animals/grayscale" ></div>
  <div class="item"><img src="https://placeimg.com/600/650/arch/sepia" ></div>
  <div class="item"><img src="https://placeimg.com/600/300/nature/grayscale" ></div>
  <div class="item"><img src="https://placeimg.com/600/400/people/sepia" ></div>
  <div class="item"><img src="https://placeimg.com/600/600/tech/grayscale" ></div>
  <div class="item"><img src="https://placeimg.com/600/200/animals/sepia" ></div>
  <div class="item"><img src="https://placeimg.com/600/700/arch/grayscale" ></div>
</div>

【讨论】:

以上是关于纯 CSS 砌体布局的主要内容,如果未能解决你的问题,请参考以下文章

纯 CSS 砌体布局

css 纯CSS砌体布局 - https://medium.com/@_jh3y/how-to-pure-css-masonry-layouts-a8ede07ba31a#.fomk5pu69

仅使用 flexbox CSS 的水平砌体布局[重复]

Bootstrap 4 - 结合网格系统的砌体布局(二、三、四和六列布局)[重复]

ASP UpdatePanel 内的砌体布局

没有预定义宽度的响应式砌体布局