那些年,我们在一起的瀑布流时光

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>

       愿你的生活美好!

上一篇 下一篇

猜你喜欢

热点阅读