「前端进阶」干货!深度解析瀑布流布局

前言

2019年终岁尾,最近对布局相关的内容比较有兴趣,在此整理一下和瀑布流相关的使用场景以及多种实现方案。

瀑布流布局

瀑布流又称瀑布流式布局,是一种比较流行的页面布局方式,专业的英文名称为[ MasonryLayouts]。与传统的分页显示不同,视觉表现为 参差不齐的多栏布局,最早是由Pinterest首先运用。

无图无真相:

如图所示,网页上呈现 参差不齐的多栏布局,图片 等宽不等高,根据图片原比例缩放直至宽度达到固定的要求,每行排满后,后面的元素依次添加到其后,视觉上显得错落有致不拘一格。

瀑布流的优点

优点如下:

  • 节省空间,外表美观,更有艺术性。
  • 对于触屏设备非常友好,通过向上滑动浏览
  • 用户浏览时的观赏和思维不容易被打断,留存更容易。

从体验的心理讲,女性是一种逛街数小时都不需要停歇的生物,一眼望不到头的瀑布流契合了这种心理。瀑布流的图片就像商品,就像逛街、就像扫货。女性只要不断往下拉伸页面,就像置身在一条没有尽头的购物街,没有层高限制的商场中一样。传统布局中的下一页就是打断,好比男朋友轻声在耳边说了句:休息一下吧,我累了……结果不言而喻~

瀑布流的缺点

缺点如下:

  • 用户无法了解内容总长度,对内容没有宏观掌控。
  • 用户无法了解现在所处的具体位置,不知道离终点还有多远。
  • 回溯时不容易定位到之前看到的内容。
  • 容易造成页面加载的负荷。
  • 容易造成用户浏览的疲劳,没有短暂的休息时间。

我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。

瀑布流的适用场景

根据瀑布流的优缺点,我们不难得出在什么情况下选择瀑布流是合理的选择:

  • 内容以图片为主的时候,瀑布流是更好的选择。图片占用空间比较大,并且大脑理解的速度相比理解文字要快,短时间内可以扫过的内容很多,所以如果用分页显示的话用户务必会频繁的翻页,影响沉浸式的体验,而瀑布流可以解决这个问题。
  • 信息与信息之间相对独立时,瀑布流是更好的选择。如果信息关联性强,用户务必会进行大量的回溯操作去查看之前或者之后的信息,相反,如果信息相对独立的话,可以使用瀑布流,让用户同时接受来自不同地方的信息。
  • 信息与搜索匹配比较模糊时,瀑布流是更好的选择。瀑布流给人的直观印象,就是同时显示的信息与用户搜索的匹配度大致一样,而分页显示的直观印象则是越靠上的信息被认为与用户的搜索越匹配。因此,当信息与搜索匹配度没有明显区分度时,可以采用瀑布流。
  • 用户目的性不强的时候,瀑布流是更好的选择。如果用户有特定需要查找的信息,分页查找定位更方便,而当目的性较弱的时候,瀑布流可以增加用户停留的时间和意想不到的收获。

multi-column 多栏布局实现瀑布流

通常 Multi-column用于文本的分列:

.container { column-count: 3;}

multi-column布局中子元素的排列顺序是先 从上往下再从左至右。

根据这个特性,我们就可以用来实现 瀑布流。

multi-column实现 瀑布流主要依赖以下几个属性:

  • column-count: 设置共有几列
  • column-width: 设置每列宽度,列数由 总宽度与 每列宽度计算得出
  • column-gap: 设置列与列之间的间距

column-count和 column-width都可以用来定义分栏的数目,而且并没有明确的优先级之分。优先级的计算取决于具体的场景。

计算方式为:计算 column-count和 column-width转换后具体的列数,哪个小就用哪个。

一个图片&文字的例子:

<div class="masonry"> <div class="item"> <img src="..."/> <span class="title">...</span> </div> <div class="item"> <img src="..."/> <span class="title">...</span> </div> <!-- more items--></div>
.masonry{ column-count: 3; column-gap: 10px;}.masonry .item{ border:1px solid #999; margin-bottom: 10px;}.masonry .item img{ width: 100%;}

在线Demo及完整代码地址:

https://codesandbox.io/s/masonry-column-1-ddbhw

效果如下:

我们可以看到,虽然实现了 瀑布流的效果,但奇怪的是例子中前两列的最后一个元素的 文本内容被 自动断开,一部分在当前列尾,一部分在下一列的列头。

我的理解是 multi-column布局会将其内的元素自动进行流动和平衡,尽可能保证每列的高度趋于相同,所以会将其内的文本阶段分布在两列内。

而这种展示方式无疑是我们不希望看到的,我们希望的是每个元素都是独立的,前后不断开,此时我们需要使用 break-inside来实现。

break-inside: auto | avoid

  • auto: 元素可以中断
  • avoid: 元素不能中断

修改一下之前的例子:

.masonry .item{ break-inside: avoid;}

在线Demo及完整代码地址:

https://codesandbox.io/s/masonry-column-2-nnw63

效果如下:

效果实现了,但由于 multi-column布局中子元素的排列顺序是先 从上往下再 从左至右,所以这种方式仅适用于数据固定不变的情况,对于滚动加载更多等可动态添加数据的情况就并不适用了。

grid 布局实现瀑布流

Grid布局是最强大的 CSS 布局方案。

它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局。以前,只能通过复杂的 CSS 框架达到的效果,现在浏览器内置了。

上图这样的布局,就是 Grid 布局的拿手好戏,因此,我们就可以用 Grid来实现 瀑布流。

为实现 瀑布流先介绍以下几个属性:

  • display:设置为 grid指明当前容器为 Grid布局
  • grid-template-columns: 定义每一列的列宽
  • grid-template-rows: 定义每一行的行高
  • column-gap:用于设置列间距

grid-template-columns和 grid-template-rows,可以使用绝对单位,也可以使用百分比。并且为了表示比例关系, Grid布局提供了 fr关键字,如果设置 1fr和 2fr,表示后者是前者的两倍。

根据以上几个属性,先写一个例子出来,看看效果:

<div class="masonry"> <div class="item"></div> <!-- more items--></div>
.masonry{ display: grid; grid-template-rows: 1fr 1fr 1fr; // 分为3行 grid-template-columns: 1fr 1fr 1fr; // 分为3列 column-gap:5px; // 列间距5px}

在线Demo及完整代码地址:

https://codesandbox.io/s/masonry-grid-1-idjpw

效果如下:

我们看到高度不同的div块分布在每一个单元格内,但还没有实现 瀑布流的效果。

为实现 瀑布流再介绍几个属性:

  • grid-row-start:上边框所在的水平 网格线
  • grid-row-end:下边框所在的水平 网格线
  • grid-column-start:左边框所在的垂直 网格线
  • grid-column-end:右边框所在的垂直 网格线

那么什么是 网格线呢?

划分网格的线,称为 网格线。水平网格线划分出行,垂直网格线划分出列。

正常情况下, n行有 n+1根水平网格线, m列有 m+1根垂直网格线,比如三行就有四根水平网格线。

上图是一个 4 x 4 的网格,共有5根水平网格线和5根垂直网格线。

这4个属性可接收如下属性:

  • auto:表示自动放置
  • 自定义名称:可以给予网格线一个名称,并在此处引用(本文并不涉及)
  • 网格线索引: 代表第几条网格线(从1开始)
  • span+数字 : 表示上下边框或左右边框跨越多少网格

来看看这个 网格线用处

为方便查看,我们让例子中的每个div块高度修改为100%,并将样式代码修改为:

.item{ height:100%;}.item:first-child{ grid-row-start:1; grid-row-end:span 2;}

在线Demo及完整代码地址:

https://codesandbox.io/s/masonry-grid-2-u5h2w

效果如下:

我们对 Grid布局中的第一项添加了 grid-row-start:1和 grid-row-end:span2,令其上边框位于1水平网格线,下边框距上边框跨越2个水平网格线。从效果上看来,是不是有点像 瀑布流了呢!

在之前的例子中,我们分别指定了 grid-template-columns和 grid-template-rows用于定义几行几列,由于行列数的确定,其内的每个单元格的宽高也被确定了,而实际的 瀑布流布局中,宽度是固定的,而高度是动态的,并且具体的行数也是无法在开始时确定的,所以我们需要在 Grid布局中不指定行高(grid-template-rows)。

介绍另一个属性:

grid-auto-rows:用来设置多余网格的行高

结合刚才说的Grid实现的瀑布流布局中,不设置行高(grid-template-rows),此时设置 grid-auto-rows后,所有单元格的高度均为 grid-auto-rows指定的值。

由于 grid-row-start和 grid-row-end可以指定单元格的上边距和下边距位置,也就是说可以将单元格的高度拉伸,而原有高度由 grid-auto-rows决定,我们仅需将 grid-auto-rows设置一个很小的值,比如 10px,然后对其进行拉伸将其高度指定为真实高度,每一个单元格都做如下操作,那么瀑布流就实现了~

假设第一个单元格内容真实高度为100px,由于 grid-auto-rows:10px,那么我们可以这样设置:

.item1{ grid-row-start:'auto'; grid-row-end:span 10;}

假设第二个单元格内容真实高度为150px,由于 grid-auto-rows:10px,那么我们可以这样设置:

.item2{ grid-row-start:'auto'; grid-row-end:span 15;}

当然了,在实际情况中, 瀑布流更多的是为图片的展示而服务的,并且由于图片是异步请求加载,只有在加载完成后才能获取图片的真实宽高,所以不得不使用JS来动态将单元格高度进行拉伸。

伪代码如下:

 //image-dom let img = document.getElementsByTagName('img')[0]; //image-dom 当前宽度 let width = img.width;
 let image = new Image(); image.src = 'xxxx.img'; image.onload = function(){ //图片原宽 let w = image.width; //图片原高 let h = image.height; //image-dom的真实高度(依据当前宽度及图片真实宽高) let height = Math.round(h * width / w) //设置当前跨越几个网格(每个网格10px) img.style.gridRowEnd = `span ${~~(height/10)}` }

在线Demo及完整代码地址:

https://codesandbox.io/s/masonry-grid-3-8smrj

效果如下:

Flexbox 实现瀑布流

Flexbox布局到今天已经是使用非常广泛的,也算是很成熟的一个特性。在此就不再介绍 Flexbox布局的相关内容,如果还有不是很了解的朋友,可参见阮一峰的《Flex 布局教程:语法篇》(http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html)

那接下来我们就看 Flexbox怎么实现瀑布流布局。

此时,我们需要将html结构设计成如下结构:

<div class="masonry"> <!-- 第一列 --> <div class="column"> <div class="item"></div> <!-- more items--> </div> <!-- 第二列 --> <div class="column"> <div class="item"></div> <!-- more items--> </div> <!-- 第三列 --> <div class="column"> <div class="item"></div> <!-- more items--> </div></div>

上面代码中 div.masonry代表当前瀑布流容器, div.column代表每一列的容器, div.item代表每一列中的每一项。

我们需要将 div.masonry和 div.column都通过 display:flex将其设置为 Flex容器。

不同的是 瀑布流容器主轴方向设置为水平方向 flex-direction:row, 列容器主轴方向设置为垂直方向 flex-direction:column

.masonry { display: flex; // 设置为Flex容器 flex-direction: row; // 主轴方向设置为水平方向}
.column { display: flex; // 设置为Flex容器 flex-direction: column; // 主轴方向设置为垂直方向}

由于当前的html结构分为了 瀑布流容器和 列容器,并且常见的需求图片均是 从左至右再 从上到下来进行排列,所以需要通过 Javascript来区分每一列的具体数据:

假设分为三列,伪代码如下:

let data1 = [], //第一列 data2 = [], //第二列 data3 = [], //第三列 i = 0;while (i < data.length) { data1.push(data[i++]); if (i < data.length) { data2.push(data[i++]); } if (i < data.length) { data3.push(data[i++]); }}return { //第一列 data1, //第二列 data2, //第三列 data3};

在线Demo及完整代码地址:

https://codesandbox.io/s/masonry-flex-1-bigeg

效果如下:

总结

做瀑布流需要考虑几方面大因素,图片质量,图片大小,加载速度,如果这些不能同时满足,会大大降低用户体验。个人觉得瀑布流对于触屏终端体验会更好一些。

本文总结了 multi-column、 grid、 Flexbox三种方式实现瀑布流,实现方案各有不同,从兼容性及易用性综合考虑,还是推荐使用 Flexbox的布局实现方案。

本文介绍的 瀑布流布局方案,本质上可称为 竖向瀑布流,篇幅有限,并未涉及 横向瀑布流的内容,关于 横向瀑布流的内容,会在接下来的文章中继续总结,敬请期待。

原文链接:https://mp.weixin.qq.com/s/CGkMY5WfbQtSCPTwwTdmPQ
作者:前端小黑屋

原文链接:,转发请注明来源!