瀑布流-前端技术(JS实现)

307 阅读3分钟

研究篇--瀑布流布局(JS实现)

写在开头的话:本文引用b站前端小夏老师的思路改进而来,以封装一个简单的vue组件来说明。

先说什么是瀑布流:

众所周知,传统的布局方式在实现多行多列,盒子高度不一致的情况下,布局是长这样的:

image.png

看到这,多多少少都会说一句,"怎么这么丑??"

而我们期望的理想型是这样的--瀑布流

image.png

那么,该如何去实现呢?你的选择又是什么?

实现的原理:

其实原理也很简单,没错,那就是 绝 ! 对 ! 定 ! 位 !

可是,一个重点的地方就是,我如何确定定位的left,top两项的值呢?如果我们搞定这两个参数,那么这个瀑布流就实现了。

我们来分析下,就假设一行三列,如下图:

image.png

我们可以根据自己设定的列数columns,以及每列间隔数gap,就能算出每列的宽度是多少

每列宽度cWidth=(盒子宽度tWidth-gap*(columns-1))/columns

根据每列宽度以及列间距gap,就能算出第一行的盒子的 lefttop

重点来了~~~

第二行要怎么确认呢?

所谓瀑布流,就是得每个盒子间间距是一样的,那么由这句话引出来的就是:

第二行开始,每一个盒子都是追加在高度"最低"的一列上,一直追加下去

加1-------

image.png

加1-------

image.png

加1-------

image.png

其中,要实现上面最关键的一个点就是,我们要实时地知道每列的高度,因为它一直在变化着。

因此我们每列添加完,就需要使用变量记录好每列的高度,在新追加的时候,判断最低的一列往上加则可!

再来说实现:

文章开头说过,此例子会以vue的组件形式来说明...

创建一个WaterFall的组件

image.png

<template>
  <div class="water-fall__root">
    <!--使用默认插槽,直接可以标签直接输入内容 -->
    <slot></slot>
  </div>
</template>

定义好组件要接收的参数props (盒子总宽度+列数+列间距)

  props: {
    // 控制瀑布布局的总宽度
    tWidth: {
      type: Number,
      default: 800,
    },
    // 显示列数
    columns: {
      type: Number,
      default: 3,
    },
    // 列间距
    cGap: {
      type: Number,
      default: 10,
    },
  },

定义data数据,记录每列的总高度

data() {
  return {
    // 每列的总高
    columnHeightArr: [],
  };
},

开始过程的实现(methods方法内)

1.为了更好的控制随意传入项的样式,我们内部统一给它们加上一个特定类名

  // 初始化添加内部控制类名
  addOwnClassName(items) {
    for (let i = 0; i < items.length; i++) {
      items[i].classList.add('w-f-item');
    }
  },

2.开始计算每列宽度和设置每个盒子的宽度

  // 计算每列宽度
  getColumnWidth(tWidth, cGap, columns) {
    return (tWidth - cGap * (columns - 1)) / columns;
  },
  
  // 设置每一个盒子的宽度
  setColumnWidth(items, width) {
    for (let i = 0; i < items.length; i++) {
      items[i].style.width = width + 'px';
    }
  },

3.设置盒子排序(思路可以参考注释,也可以翻看上面原理部分)

   // 设置列的排序
   setColumnSort(items, cWidth) {
     for (let i = 0; i < items.length; i++) {
       // 先排第一行
       // 设置top,left
       if (i < this.columns) {
         items[i].style.top = 0;
         items[i].style.left = i * (cWidth + this.cGap) + 'px';
         this.columnHeightArr.push(items[i].offsetHeight);
       } else {
         // 如果是>columns,就算第二行开始的
         // 先判断哪列高度最低
         const lowIndex = this.columnHeightArr.indexOf(
           Math.min(...this.columnHeightArr)
         );
         // 得到left
         items[i].style.left = lowIndex * (cWidth + this.cGap) + 'px';
         // 得到top
         items[i].style.top =
           this.columnHeightArr[lowIndex] + this.cGap + 'px';
         // 更新每列的高度
         this.columnHeightArr[lowIndex] += this.cGap + items[i].offsetHeight;
       }
     }
   },  

4.然后定义一个更新的方法,用于集成调用上述定义的功能方法

  // 更新,重新排列
  updateFn() {
    //每次更新重新置空一次高度数组,重新计算
    this.columnHeightArr=[]
    //获取父容器的dom
    const rDom = document.querySelector('.water-fall__root');
    //设置父容器的宽度
    rDom.style.width = this.tWidth + 'px';
    // 获取所有的子项
    const items = document.querySelector('.water-fall__root').children;
    // 每个子项都加上内部类名 w-f-item
    this.addOwnClassName(items);
    // 计算每列的宽度(总的宽度-全部间距)/列数
    const cWidth = this.getColumnWidth(this.tWidth, this.cGap, this.columns);
    // 设置每一个的宽度
    this.setColumnWidth(items, cWidth);
    // 设置列的排序(如果有图片,因图片高度需要加载之后才有,所以延时处理)
    setTimeout(() => {
      this.setColumnSort(items, cWidth);
    }, 100);
  },

5.选择在mounted钩子函数调用updateFn

mounted() {
  this.updateFn();
},

样式部分

.water-fall__root {
  position: relative;
  .w-f-item {
    position: absolute;
    img {
      width: 100%;
      height: 100%;
    }
  }
}

最终--组件使用

<template>
  <div>
    <WaterFall :tWidth="1000" :columns="3" :cGap="5">
      <!-- item内容 -->
      <div v-for="item in imgArr" :key="item">
        <img :src="item" alt="">
      </div>
    </WaterFall>
  </div>
</template>

<script>
export default {
  data() {
    return {
     imgArr:[
      require('../src/assets/images/01.webp'),
      require('../src/assets/images/02.webp'),
      require('../src/assets/images/03.webp'),
      require('../src/assets/images/04.webp'),
      require('../src/assets/images/05.webp'),
      require('../src/assets/images/06.webp'),
      require('../src/assets/images/07.webp'),
      require('../src/assets/images/08.webp'),
     ]
    };
  },
  methods: {
   
  },
};
</script>

<style></style>

image.png

完结 ! 再次感谢前端小夏老师提供的思路,也望各位同学都能得到感悟。