那些年,我们在一起的瀑布流时光
2020-06-10 本文已影响0人
雪燃归来
首先,我要说声抱歉,可能是这个标题让您才点开了这篇文章,本文中并没有什么浪漫的事情,只是记录了一些我对css布局(瀑布流布局)的一点心得。如果你对这个不感兴趣,我很抱歉耽误了您的时间,并消耗了您的流量。
对于瀑布流的实现方式有很多,我这里主要介绍两种简单瀑布流和动态计算布局的瀑布流。
一、简单瀑布流
正如这个名字一样,简单瀑布流思路很简单,实现的方式也很简单。其原理就是将列表按照列分成多个list,然后针对每个列单独渲染数据。如果您不明白我的意思,请您看下面的图:
相信您现在明白了吧,很简单,正如图中的页面,我们把整个产品区域分成两列,在渲染数据的时候让里边图片的高度宽度相同,高度自适应,即可实现我们想要的瀑布流效果。由于代码比较简单,此处省略。
这种方法虽然简单,但是在开发中并不是经常使用,不是大家喜欢装大神,而是,它有一种致命的缺陷,就是**如果某一列商品连续出现图片高度很小/很大的情况下,就会出现样式失控的状况,具体的情形,您可以脑补一下。
二、动态计算布局的瀑布流
由于第一种实现瀑布流的方式有非常大的缺陷,所以我们推荐使用这一种方式。其实现原理是多个宽度相同,但是高度不同的Item,按照从上到下,从左到右的顺序进行排列。
其具体的实现步骤如下
1、创建商品列表的基本html和css,让item相对于goods(div)进行排列
2、生成不同高度的图片,撑起不同高度的item
3、计算item的位置,来达到 从上到下,从左到右排列的目的
动态计算布局的瀑布流
这里我再究图补充一点,在一个新的item(以item3为例)要进行位置计算的时候,先要判断左边列表高度和右边列表高度中,哪一个更小,如果左边更小,我们就把item3放到左边,反之亦然。下面我就使用使用vue2.X在实现瀑布流效果,这里我们假设您有比较很好的vue基础。
1、我们需要获取列表数据,这里我们以假数据示范。
data:
// 数据源
dateSource: [],
methods:
initData(){
/*
this.$http.get('/goods').then(data => {
this.dateSource = data.llist
})
*/
this.dateSource = [
{
img:'http://imooc.res.lgdsunday.club/goods-1.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-2.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-3.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-4.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-1.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-2.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-3.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-4.jpg',
desc: 'my love my love'
}
]
this.initImgStyle()
// 调用创建瀑布流的方法(等到dom创建完成后)
this.$nextTick(() => {
this.initWaterfall()
})
}
2、获取完数据之后,我们就需要渲染模版,这也是后续所有工作的基础
<div class="goods goods-waterfall" >
<div class="good-item goods-waterfall-item"
v-for="(item, index) in dateSource"
ref="goodsItem"
:key="index">
<img
:src="item.img"
:alt="item.desc">
</div>
</div>
3、添加样式
一定要把样式添加进去,否则我们后面所有的操作将不能进行。
.goods-waterfall{
position: relative;
}
.goods-waterfall-item{
position: absolute;
}
.goods{
display: flex;
justify-content: space-between;
flex-wrap: wrap;
background: #cccccc;
}
.good-item{
flex-basis: 49%;
width: 49%;
text-align: center;
background: #ffffff;
overflow: hidden;
img{
width: 95%;
}
p{
font-size: 14px;
}
}
4、动态计算列表中每个Item中图片的高度
data:
// 图片样式集合
imgStyles: [],
// 最大高度
MAX_IMG_HEIGHT: 200,
// 最小高度
MIN_IMG_HEIGHT: 80,
methods:
/*
*返回随机图片高度
*/
imgHeight(){
let result = Math.floor(Math.random() * (this.MAX_IMG_HEIGHT - this.MIN_IMG_HEIGHT) + this.MIN_IMG_HEIGHT)
return result
},
/*
*根据图片随机高度,生成对应图片的样式数据
* */
initImgStyle(){
this.dateSource.forEach(item => {
let imgHeight = this.imgHeight() + 'px'
console.log(imgHeight)
this.imgStyles.push({
height: imgHeight
})
})
},
5、计算瀑布流的位置样式信息
这一步骤是我们整个逻辑的核心,比较复杂,难度适中,只要你慢慢掌握原理,就可以很完美的实现。请您结合代码的注释一同阅读,效果会更好。
data:
// item margin
ITEM_MARGIN_SIZE: 18,
// item样式集合
goodsItemStyles: [],
// WaterFall 组件的高度
goodsViewHeight: 0
methods:
/*
*瀑布流布局
* 1、创建商品列表的基本html和css,让item相对于goods(div)进行排列
2、生成不同高度的图片,撑起不同高度的item
3、计算item的位置,来达到 从上到下,从左到右排列的目的
如果左测高度小于右侧的高度(leftHeightTotal <= rightHeightTotal),此时item距离左侧为0,距离顶部为当前的leftHeightTotal
如果左侧的高度大于右侧的高度(leftHeightTotal > rightHeightTotal),此时item距离右侧为0,距离顶部为当前的rightHeightTotal
4、保存计算出的 item的所有样式,最大的高度为goods组件的高度
* */
initWaterfall(){
let $goodsItems = this.$refs.goodsItem
if(!$goodsItems) return
let leftHeightTotal = 0, rightHeightTotal = 0
$goodsItems.forEach(($el, index) => {
let goodsItemStyle = {}
let elHeight = $el.clientHeight + this.ITEM_MARGIN_SIZE
if(leftHeightTotal <= rightHeightTotal){
goodsItemStyle = {
right: '0px',
top: leftHeightTotal + 'px'
}
//更新左侧的高度
leftHeightTotal += elHeight
} else {
goodsItemStyle = {
left: '0px',
top: rightHeightTotal +'px'
}
//更新右侧的高度
rightHeightTotal += elHeight
}
this.goodsItemStyles.push(goodsItemStyle)
})
this.goodsViewHeight = (leftHeightTotal > rightHeightTotal ? leftHeightTotal : rightHeightTotal) + 'px'
}
6、重新渲染模版,将imgStyles、goodsItemStyles、goodsViewHeight渲染到对应的元素节点上。
<div class="goods goods-waterfall" :style="{height: goodsViewHeight}">
<div class="good-item goods-waterfall-item"
v-for="(item, index) in dateSource"
:style="goodsItemStyles[index]"
ref="goodsItem"
:key="index">
<img
:src="item.img"
:style="imgStyles[index]"
:alt="item.desc">
</div>
</div>
至此,我们所有的工作就完成了,我们以刷新一下页面,看一下我们作品。
瀑布流
。
为了照顾某些懒得看文章内容的同学,下面我将代码附加出来。
<template>
<div>
<div class="goods goods-waterfall" :style="{height: goodsViewHeight}">
<div class="good-item goods-waterfall-item"
v-for="(item, index) in dateSource"
:style="goodsItemStyles[index]"
ref="goodsItem"
:key="index">
<img
:src="item.img"
:style="imgStyles[index]"
:alt="item.desc">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'WaterFall',
data () {
return {
// 数据源
dateSource: [],
// 图片样式集合
imgStyles: [],
// 最大高度
MAX_IMG_HEIGHT: 200,
// 最小高度
MIN_IMG_HEIGHT: 80,
// item margin
ITEM_MARGIN_SIZE: 8,
// item样式集合
goodsItemStyles: [],
// WaterFall 组件的高度
goodsViewHeight: 0
}
},
created(){
this.initData()
},
methods: {
/*
*获取数据
* */
initData(){
/*
this.$http.get('/goods').then(data => {
this.dateSource = data.llist
})
*/
this.dateSource = [
{
img:'http://imooc.res.lgdsunday.club/goods-1.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-2.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-3.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-4.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-1.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-2.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-3.jpg',
desc: 'my love my love'
},
{
img:'http://imooc.res.lgdsunday.club/goods-4.jpg',
desc: 'my love my love'
}
]
this.initImgStyle()
// 调用创建瀑布流的方法(等到dom创建完成后)
this.$nextTick(() => {
this.initWaterfall()
})
},
/*
返回随机图片高度
*/
imgHeight(){
let result = Math.floor(Math.random() * (this.MAX_IMG_HEIGHT - this.MIN_IMG_HEIGHT) + this.MIN_IMG_HEIGHT)
return result
},
/*
*根据图片随机高度,生成对应图片的样式数据
* */
initImgStyle(){
this.dateSource.forEach(item => {
let imgHeight = this.imgHeight() + 'px'
console.log(imgHeight)
this.imgStyles.push({
height: imgHeight
})
})
},
/*
*瀑布流布局
* 1、创建商品列表的基本html和css,让item相对于goods(div)进行排列
2、生成不同高度的图片,撑起不同高度的item
3、计算item的位置,来达到 从上到下,从左到右排列的目的
如果左测高度小于右侧的高度(leftHeightTotal <= rightHeightTotal),此时item距离左侧为0,距离顶部为当前的leftHeightTotal
如果左侧的高度大于右侧的高度(leftHeightTotal > rightHeightTotal),此时item距离右侧为0,距离顶部为当前的rightHeightTotal
4、保存计算出的 item的所有样式,最大的高度为goods组件的高度
* */
initWaterfall(){
let $goodsItems = this.$refs.goodsItem
if(!$goodsItems) return
let leftHeightTotal = 0, rightHeightTotal = 0
$goodsItems.forEach(($el, index) => {
let goodsItemStyle = {}
let elHeight = $el.clientHeight + this.ITEM_MARGIN_SIZE
if(leftHeightTotal <= rightHeightTotal){
goodsItemStyle = {
right: '0px',
top: leftHeightTotal + 'px'
}
//更新左侧的高度
leftHeightTotal += elHeight
} else {
goodsItemStyle = {
left: '0px',
top: rightHeightTotal +'px'
}
//更新右侧的高度
rightHeightTotal += elHeight
}
this.goodsItemStyles.push(goodsItemStyle)
})
this.goodsViewHeight = (leftHeightTotal > rightHeightTotal ? leftHeightTotal : rightHeightTotal) + 'px'
}
}
}
</script>
<style ang="scss" scoped>
.goods-waterfall{
position: relative;
}
.goods-waterfall-item{
position: absolute;
}
.goods{
display: flex;
justify-content: space-between;
flex-wrap: wrap;
background: #cccccc;
}
.good-item{
flex-basis: 49%;
width: 49%;
text-align: center;
background: #ffffff;
overflow: hidden;
img{
width: 95%;
}
p{
font-size: 14px;
}
}
</style>
愿你的生活美好!