前言

瀑布流布局是什么呢?应用于什么场景呢?
简单描述一下,就是,瀑布流布局就是宽度一致,但是高度不限定死,最终通过算法排列,使得布局出现一种上下交错显示的感觉,看起来像是瀑布一样。
应用场景:可以说非常多了,比如最常见的淘宝,蘑菇街等都是瀑布流布局
做前端的肯定都会遇到这种情况需要使用瀑布流布局
那么今天我就专门整理一个好用的瀑布流布局实现方法
以后小伙伴们在需要做到这个布局的时候可以直接来着复制去改改就可以使用了。【建议收藏】

效果图

效果图内放上三张图片,一张是pc端的5列布局。一种是移动端手机的2列布局,一种是pad端的2列布局

PC端的

android 瀑布流实现方式 瀑布流 vue_vue.js

手机端的

android 瀑布流实现方式 瀑布流 vue_javascript_02

pad端的

android 瀑布流实现方式 瀑布流 vue_javascript_03

上代码

这里代码可以直接一键复制丢到你们的项目上看看效果哈。
简单描述一下实现逻辑,代码内有注释详细看得懂。
逻辑:通过定位来实现每一个盒子的位置排列。
然后通过js的计算把每一列的长短都计算好,以此让下一个循环出的盒子永远放在最短的那一列下面
保证列不会出现一列非常长,另外一列很短的情况。

手机和电脑切换的时候刷新一下,因为可能布局会有点乱掉了。正常设备大小会自适应的。

<template>
  <div class="tab-container" id="tabContainer">
    <div class="tab-item" v-for="(item, index) in pbList" :key="index">
      <img :src="item.url" />
      <div>
        <p class="content">{{ item.content }}</p>
        <div class="tips">
          <div class="tipsLeft">
            <span style="color: #f56c6c">¥</span>
            <span style="color: #f56c6c; font-size: 22px; margin-left: 10px">{{
              item.money
            }}</span>
          </div>
          <div class="tipsRight">销量: {{ item.sales_volume }}</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      //瀑布流数据
      pbList: [
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
          title: "标题",
          content:
            "描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "68.50",
          sales_volume: "281",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
          title: "标题",
          content: "描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "35.00",
          sales_volume: "1221",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
          title: "标题",
          content:
            "描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "68.50",
          sales_volume: "281",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
          title: "标题",
          content: "描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "35.00",
          sales_volume: "1221",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
          title: "标题",
          content:
            "描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "68.50",
          sales_volume: "281",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
          title: "标题",
          content: "描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "35.00",
          sales_volume: "1221",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
          title: "标题",
          content:
            "描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "68.50",
          sales_volume: "281",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
          title: "标题",
          content: "描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "35.00",
          sales_volume: "1221",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
          title: "标题",
          content:
            "描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "68.50",
          sales_volume: "281",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
          title: "标题",
          content: "描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "35.00",
          sales_volume: "1221",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
          title: "标题",
          content:
            "描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "68.50",
          sales_volume: "281",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
          title: "标题",
          content: "描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "35.00",
          sales_volume: "1221",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
          title: "标题",
          content:
            "描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "68.50",
          sales_volume: "281",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
          title: "标题",
          content: "描述部分描述部分描述部分描述部分描述部分描述部分描述部分",
          money: "35.00",
          sales_volume: "1221",
        },
      ],
    };
  },
  mounted() {
    this.waterFall("#tabContainer", ".tab-item"); //实现瀑布流
    // 窗口变化自适应布局
    window.onresize = () => {
      return (() => {
        this.waterFall("#tabContainer", ".tab-item");
      })();
    };
  },
  methods: {
    /**
     * @param { string } wrapIdName    容器id(或class)名称
     * @param { string } contentIdName 容器中内容项id(或class)名称
     * @param { number } column        容器中内容展示列数 手机的话建议改为2
     * @param { number } columnGap     容器中 列 间隔距离 默认为20
     * @param { number } rowGap        容器中 行 间隔距离 默认为20
     */

    //瀑布流方法:通过拿到dom循环,给每一个dom添加对应的定位位置排列出瀑布流布局。
    //通过判断列的高度,来把下一个盒子放在最短的地方补上
    waterFall(
      wrapIdName,
      contentIdName,
      columns = 5,
      columnGap = 20,
      rowGap = 20
    ) {
      // 获得内容可用宽度(去除滚动条宽度)
      const wrapContentWidth =
        document.querySelector(wrapIdName).offsetWidth - 8;

      // 间隔空白区域
      const whiteArea = (columns - 1) * columnGap;

      // 得到每列宽度(也即每项内容宽度)
      const contentWidth = parseInt((wrapContentWidth - whiteArea) / columns);

      // 得到内容项集合
      const contentList = document.querySelectorAll(contentIdName);

      // 成行内容项高度集合
      const lineConentHeightList = [];

      for (let i = 0; i < contentList.length; i++) {
        // 动态设置内容项宽度
        contentList[i].style.width = contentWidth + "px";

        // 获取内容项高度
        const height = contentList[i].clientHeight;

        if (i < columns) {
          // 第一行按序布局
          contentList[i].style.top = 0;
          contentList[i].style.left = contentWidth * i + columnGap * i + "px";

          // 将行高push到数组
          lineConentHeightList.push(height);
        } else {
          // 其他行
          // 获取数组最小的高度 和 对应索引
          let minHeight = Math.min(...lineConentHeightList);
          let index = lineConentHeightList.findIndex(
            (listH) => listH === minHeight
          );

          contentList[i].style.top = minHeight + rowGap + "px";
          contentList[i].style.left = (contentWidth + columnGap) * index + "px";

          // 修改最小列的高度 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 行间距
          lineConentHeightList[index] += height + rowGap;
        }
      }
    },
  },
};
</script>

<style scoped>
* {
  margin: 0;
  padding: 0;
}
/* 最外层大盒子 */
.tab-container {
  position: relative;
}
/* 每个小盒子 */
.tab-container .tab-item {
  position: absolute;
  height: auto;
  border: 1px solid #ccc;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
  background: white;
  /* 元素不能中断显示 */
  break-inside: avoid;
  text-align: center;
}
.tab-container .tab-item img {
  width: 100%;
  height: auto;
}
/* 描述 */
.content {
  text-align: left;
  color: #5c5c5c;
  font-size: 14px;
  margin-top: 10px;
  padding: 0 10px 0 10px;
  overflow: hidden;
  text-overflow: ellipsis;
  /* 将对象作为弹性伸缩盒子模型显示 */
  display: -webkit-box;
  /* 限制在一个块元素显示的文本的行数 */
  /* -webkit-line-clamp 其实是一个不规范属性,使用了WebKit的CSS扩展属性,该方法适用于WebKit浏览器及移动端;*/
  -webkit-line-clamp: 2;
  /* 设置或检索伸缩盒对象的子元素的排列方式 */
  -webkit-box-orient: vertical;
}
/* 价格和销量 */
.tips {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 10px;
  margin: 10px 0;
}
.tipsRight {
  padding: 3px 7px;
  background-color: #ff9a00;
  color: #fff;
  border-radius: 8px;
  font-size: 14px;
}
</style>