瀑布流布局有一个专业的英文名称Masonry Layouts。瀑布流布局已经有好多年的历史了,我最早知道这个名词的时候大约是在2012年,当时Pinterest网站的布局就是使用的这种流式布局,简言之像Pinterest网站这样的布局就称之为瀑布流布局,也有人称之为Pinterest 布局。
瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式。即多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次按照规则放入指定位置。
瀑布流布局的核心是基于一个网格的布局,而且每行包含的项目列表高度是随机的(随着自己内容动态变化高度),同时每个项目列表呈堆栈形式排列,最为关键的是,堆栈之间彼此之间没有多余的间距差存大。还是上张图来看看我们说的瀑布流布局是什么样子。
1、为什么使用瀑布流
瀑布流布局在我们现在的前端页面中经常会用的到,它可以有效的降低页面的复杂度,节省很多的空间,对于整个页面不需要太多的操作,只需要下拉就可以浏览用户需要看到的数据;并且,在当前这个APP至上的时代,瀑布流可以提供很好的用户体验,通过结合下拉刷新,上拉加载进行数据的懒加载等操作,对于用户的体验感来说是接近于满分的!
2、瀑布流的特点
其实瀑布流的特点就是参差不齐的排列方式,以及流式布局的扩展性,可以通过界面展示给用户多条数据,并且让用户可以有向下浏览的冲动。
1、纯 css 瀑布流:( multi-columns 方法 )
首先最早尝试使用纯CSS方法解决瀑布流布局的是CSS3 的Multi-columns。
其最早只是用来用来实现文本多列排列(类似报纸杂志样的文本排列)。但对于前端同学来说,他们都是非常具有创意和创新的,有人尝试通过Multi-columns相关的属性column-count
、column-gap
配合break-inside
来实现瀑布流布局。
/* 这里是第一次接触到 column-columns 这个属性,这是一个可以设置将div元素中的文本分成几列 */ /* 默认值是:auto */ column-count:3; -moz-column-count:3; /* Firefox */ -webkit-column-count:3; /* Safari and Chrome */ /* 注意:IE9及更早 IE 版本浏览器不支持 column-count 属性 */ /* 这里还会用到另一个属性 column-gap,用来调整边距,实现瀑布流布局 */
.demo-1{ -moz-column-count:3; /* Firefox */ -webkit-column-count:3; /* Safari 和 Chrome */ column-count:3; -moz-column-gap: 1em; -webkit-column-gap: 1em; column-gap: 1em; width: 80%; margin:0 auto; } .item { padding: 2em; margin-bottom: 2em; -webkit-column-break-inside: avoid; break-inside: avoid; /*防止断点*/ background: #ccc; text-align: center; }
column-count
和column-gap
,前者用来设置列数,后者设置列间距。
上面控制了列与列之间的效果,但这并不是最关键之处。当初纯CSS实现瀑布流布局中最关键的是堆栈之间的间距,而并非列与列之间的控制(说句实话,列与列之间的控制float
之类的就能很好的实现)。找到实现痛楚,那就好办了。或许你会问有什么CSS方法可以解决这个。在CSS中有一个break-inside
属性,这个属性也是实现瀑布流布局最关键的属性。
其中break-inside:avoid
为了控制文本块分解成单独的列,以免项目列表的内容跨列,破坏整体的布局。当然为了布局具有响应式效果,可以借助媒体查询属性,在不同的条件下使用column-count
设置不同的列。
但是这里还是有个弊端,这并不符合瀑布流的原理,如果使用纯css写瀑布流,则每一块都是从上往下排列,不能做到从左到右排列,最主要的是不会识别哪一块图片放在哪个地方合适,若是再配合动态加载,效果会特别不好,所以只能通过JS来实现瀑布流。
2、瀑布流的位置分析图解
那么这里用图片来分析一下我们想要的瀑布流是什么样的。
如下方图片。假设一排放5张图片。当第一排排满足够多的等宽图片时,显示的是这样的。那么假如我们要放第6张图片的时候,应该放在什么位置呢?
如果按照我们的正常逻辑来想,应该是放在第一张图片下面,依次水平排列过去(如下图)
但现实并非如此!在瀑布流中,从第2行开始,接下去的每一张图片都会放在上行中高度最低的那一列图片下方。(如下图)
为什么呢?因为放置它之前,这一列的高度为所有列中最小,所以会放置在这个地方。
那么如果再继续放置下去,第七张图片应该放在第三列图片下方,以此类推。
所以每次加载图片时,会需要判断哪一列的图片累计的高度最小,那么下一张图片就放在哪一列,即瀑布流算法去判断图片的确定位置。
3、JS实现
结构示意图:
可扩展要素:(1)外层容器的box高度不固定;(2)每列col的宽度可自定义;(3)列数可自定义,取决于有几个数据dataList
,每个列的数据对应一个 dataList。
(1)页面布局结构代码
{{item.text}} {{item.text}} {{item.text}} {{item.text}}
(2)加载数据
当组件mounted
时获取数据,获取到数据后执行mountMenu()
方法,mountMenu()
方法将会通过selectCol()
选择当前高度最小的列,并把数据push
到对应的dataList
中,mountMenu()会在每次执行时递归调用,直到遍历完所有的数据。
export default { data() { return { mainMenuList: [], dataList1: [], dataList2: [], dataList3: [], dataList4: [], } }, mounted() {this.mountMenu() }, methods: { mountMenu(arg) { var temp = this.mainMenuList var index = arg || 0 var refName = this.selectCol() if (temp.length > index) { this[refName].push(this.mainMenuList[index]) this.$nextTick(() => { this.mountMenu(index + 1) }) } }, selectCol() { var getHeight = (ref) => { return this.$refs[ref].offsetHeight } var height1 = getHeight('col1') var height2 = getHeight('col2') var height3 = getHeight('col3') var height4 = getHeight('col4') switch (Math.min(height1, height2, height3, height4)) { case height1: return 'dataList1' break case height2: return 'dataList2' break case height3: return 'dataList3' case height4: return 'dataList4' break } }, } }
这样就可以实现一个我们想要的瀑布流了,但是不好的是频繁的取高度,会导致频繁的回流。所以如果不是一定要使用的话,就尽量避免吧。