【前端】关于前端瀑布流的几种实现方案

1. 使用多列布局

多列布局是一种十分简单的实现瀑布流的方法,并且兼容性也很好。

html:

html 复制代码
<template>
  <div class="app">
    <div v-for="(item, i) in imgList" :key="item.img" class="item">
      <img :src="item.img" alt="" />
      <p>{{ i }}</p>
    </div>
  </div>
</template>

为.app 添加column-count属性,.app容器会把内容分为指定数量列,容器内的内容会平均分配在指定的列中,column-gap 可以指定每个列之间的间距。

css 复制代码
.app {
  column-count: 5;
  column-gap: 10px;
  background-color: aquamarine;

  .item {
    height: fit-content;
    overflow: hidden;
    position: relative;
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    p {
      position: absolute;
      left: 0;
      top: 0;
      background-color: #000;
      color: #fff;
      font-weight: 600;
    }
  }
}

效果图:

多列布局瀑布流的缺点:

  1. 多列布局中的元素是从上至下排列的,不是常规的从左至右,有可能会导致应该排在前面的元素在最后显示。

  2. 多列布局的响应式兼容不是很好。

我们可以通过媒体查询来完善兼容性:

css 复制代码
@media screen and (max-width: 1920px) {
 .app {
   column-count: 5;
 }
}
@media screen and (max-width: 1200px) {
 .app {
   column-count: 4;
 }
}
@media screen and (max-width: 800px) {
 .app {
   column-count: 3;
 }
}
@media screen and (max-width: 500px) {
 .app {
   column-count: 2;
 }
}

效果图:

2. 使用flex布局(横向瀑布流)

我认为这个方法除了只能横向瀑布流布局应该也没其他缺点了。

scss:

css 复制代码
.app {
  display: flex;
  flex-wrap: wrap;
  background-color: aquamarine;

  .item {
    position: relative;
    overflow: hidden;
    height: 200px;
    width: fit-content;
    margin-bottom: 10px;
    margin-right: 10px;
    flex-grow: 1; // 平均分配剩余空间

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    p {
      position: absolute;
      left: 0;
      top: 0;
      background-color: #000;
      color: #fff;
      font-weight: 600;
    }
  }
}

为父元素添加display: flex; 子元素添加flex-grow:1; ,子元素横向排列并且自动填充空白区域。flex-wrap: wrap;控制自动换行。

演示:

3. 使用Grid布局

grid瀑布流结合了css和js,算是一个比较中规中矩的方案吧,实现起来也比较简单。

实现效果:

css代码:

css 复制代码
.app {
  background-color: aquamarine;

  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
  grid-auto-rows: 1px;
  grid-gap: 0 10px;
  .item {
    height: fit-content;
    overflow: hidden;
    position: relative;
    grid-row-start: auto;
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    p {
      position: absolute;
      left: 0;
      top: 0;
      background-color: #000;
      color: #fff;
      font-weight: 600;
      font-size: 2vw;
    }
  }
}

grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); 规定了在同一行的图片最小500px,最大1fr,自动填充剩余空间。

grid-auto-rows: 1px; 的作用是把每一个网格轨道高度划为1px,方便后面js可以更好的计算。

grid-gap: 0 10px;规定行之间间隙为0,列之间的间隙为10px,为什么需要设置行之间的间隙为0呢,因为每行为1px,每个元素会占用很多行,设置了行间距,每行之间的距离会变得很大。

js代码:

js 复制代码
function () {
    const items = document.querySelectorAll(".item");
    items.forEach((item) => {
      const height = Number.parseInt(
        getComputedStyle(item).getPropertyValue("height")
      );
      item.style.height = height + "px";
      item.style.gridRowEnd = "span " + (height + SPACE);
    });
  };

核心思想是通过设置 grid-row-end 属性来实现瀑布流效果。具体而言,将其设置为 "span (高度 + SPACE)",其中 SPACE 表示每个 item 的行间距,为10。这样就可以将元素的高度加上行间距后转换为行的数量(每行高度为1px)。这样一来,就设置了每个元素的真实高度。

至于为什么要设置 grid-auto-rowsgrid-row-end 属性,是因为如果不设置的话,grid 布局会将每一行的高度统一为最高的元素的高度,这样就无法实现不同行高度的瀑布流效果。通过设置 grid-row-end 属性,可以确保每个元素真正占用的高度,后续元素可以紧贴在上一个元素的下方,以实现瀑布流的布局效果。

如果grid-auto-rows: 为1fr:

可以看见,下面的元素不会填充上面的空白,每行的高度就是最高的元素的高度。

完整代码:

js 复制代码
<template>
  <div class="app">
    <div v-for="(item, i) in imgList" :key="item.img" class="item">
      <img :src="item.img" alt="" />
      <p>{{ i }}</p>
    </div>
  </div>
</template>
<script setup>
import { onMounted, ref } from "vue";

const imgList = ref(
  randomArr([
    {
      img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
    },
    {
      img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
    },
    {
      img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
    },
  ])
);

function randomArr(arr) {
  return arr.sort(() => (Math.random() > 0.5 ? 1 : -1));
}

onMounted(() => {
  const SPACE = 10;

  window.onload = function () {
    const items = document.querySelectorAll(".item");
    items.forEach((item) => {
      const height = Number.parseInt(
        getComputedStyle(item).getPropertyValue("height")
      );
      item.style.height = height + "px";
      item.style.gridRowEnd = "span " + (height + SPACE);
    });
  };
});
</script>

<style lang="scss" scoped>
.app {
  background-color: aquamarine;

  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
  grid-auto-rows: 1fr;
  grid-gap: 0 10px;
  .item {
    height: fit-content;
    overflow: hidden;
    position: relative;
    grid-row-start: auto;
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    p {
      position: absolute;
      left: 0;
      top: 0;
      background-color: #000;
      color: #fff;
      font-weight: 600;
      font-size: 2vw;
    }
  }
}
</style>

使用Position定位实现

Position定位大量依赖js,性能比较差,但是是目前瀑布流的主要实现方案。

①. 首先根据最小宽度,计算这个容器可以放下多少列,以及计算容器多出来的宽度可以平分给每列多少:

js 复制代码
const MIN_WIDTH = 200; // 每列的最低宽度
const SPACE = 10; // 行和列的间隔

/**
 * 计算适合的列,以及每列的膨胀值
 */
function calc() {
  // 获取容器宽度
  const width = Number.parseInt(
    getComputedStyle(document.querySelector(".app")).getPropertyValue("width")
  );
  let colNum = width / (MIN_WIDTH + SPACE);

  // 计算容器多出来的宽度可以平分给每列多少
  const expension = (width - colNum * (MIN_WIDTH + SPACE)) / colNum;

  return {
    colNum,
    expension,
  };
}

②:根据算法设置元素位置:

js 复制代码
/**
 * 设置位置
 */
function setPosition() {
  // 首先调用calc函数获取基本信息:
  const info = calc();
  // 根据info.colNum生成数组,记录每列的最高高度。
  const topList = new Array(info.colNum);
  // 填充0
  topList.fill(0);
  // 计算膨胀后的宽度
  const EXPENSION_WIDTH = MIN_WIDTH + info.expension;
  // 获取所有的瀑布流item
  const items = document.querySelectorAll(".item");

  // 遍历
  items.forEach((item) => {
    // 首先获取topList中最矮的列,让item优先填充最矮的列
    const minHeight = Math.min(...topList);

    // 获取minHeight的位置
    const index = topList.indexOf(minHeight);
    // 计算元素距离左边的值
    const left = (EXPENSION_WIDTH + SPACE) * index;
    // 设置元素的transform
    item.style.transform = `translate(${left}px,${minHeight}px)`;
    // 获取元素的高度
    const itemHeight = Number.parseInt(
      getComputedStyle(item).getPropertyValue("height")
    );

    // 更新高度,注意,要加上膨胀后的高度。
    topList[index] += (EXPENSION_WIDTH * itemHeight) / MIN_WIDTH + SPACE;

    // 更新元素的宽度
    item.style.width = EXPENSION_WIDTH + "px";
  });

  // 获取父元素,设置高度为最高列的高度。
  const app = document.querySelector(".app");
  app.style.height = Math.max(...topList) + "px";
}

算法大致是:topList中的每一个位置记录着对应列的高度,每次遍历都会让元素去最矮的那一列填充,然后更新列的高度,但是,容器中可能会多出来一部分的元素,这个时候就要把这部分多出来的空间平分给每一列,所以,每一列的实际宽度是MIN_WIDTH 加上 平分的宽度。

这里要注意,每当一张图片加载完毕之后,都要重新计算位置,不然等到所有的元素都加载完才计算就会很不美观,并且宽度改变的时候也要重新计算。

完整代码:

html 复制代码
<template>
  <div class="app">
    <div v-for="(item, i) in imgList" :key="item.img" class="item">
      <img @load="setPosition" :src="item.img" alt="" />
      <p>{{ i }}</p>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted } from "vue";

const imgList = ref([
  {
    img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/28/5b78e5ac5e3744b6a5bfbbd7d8d41d7b.png",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/8d3e4b89cb4b44bb91baec5bb5311db3.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/62ea40fbd4a24743b2f4780f80bd3ff0.png",
  },
  {
    img: "https://ts1.cn.mm.bing.net/th/id/R-C.07a7f5800d36b20bc6f5b255f389bfd4?rik=OOTL%2b5c6kufU6g&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fphotoblog%2f1009%2f03%2fc0%2f5069852_5069852_1283447495046.jpg&ehk=N835az5AWRlUec59qpFJVJzTHeur9NApCHjnu5gGBao%3d&risl=&pid=ImgRaw&r=0",
  },
  {
    img: "http://43.143.250.198:8081/static/2023/10/31/bd6452b755d642e79e572ee574609564.png",
  },
]);

const MIN_WIDTH = 500; // 每列的最低宽度
const SPACE = 10; // 行和列的间隔

/**
 * 计算适合的列,以及每列的膨胀值
 */
function calc() {
  // 获取容器宽度
  const width = Number.parseInt(
    getComputedStyle(document.querySelector(".app")).getPropertyValue("width")
  );
  let colNum = Math.floor(width / (MIN_WIDTH + SPACE));

  // 计算容器多出来的宽度可以平分给每列多少
  const expension = Math.floor((width - colNum * (MIN_WIDTH + SPACE)) / colNum);
  return {
    colNum,
    expension,
  };
}
/**
 * 设置位置
 */
function setPosition() {
  // 首先调用calc函数获取基本信息:
  const info = calc();
  // 根据info.colNum生成数组,记录每列的最高高度。
  const topList = new Array(info.colNum);
  // 填充0
  topList.fill(0);
  // 计算膨胀后的宽度
  const EXPENSION_WIDTH = MIN_WIDTH + info.expension;
  // 获取所有的瀑布流item
  const items = document.querySelectorAll(".item");

  // 遍历
  items.forEach((item) => {
    // 首先获取topList中最矮的列,让item优先填充最矮的列
    const minHeight = Math.min(...topList);

    // 获取minHeight的位置
    const index = topList.indexOf(minHeight);
    // 计算元素距离左边的值
    const left = (EXPENSION_WIDTH + SPACE) * index;
    // 设置元素的transform
    item.style.transform = `translate(${left}px,${minHeight}px)`;
    // 获取元素的高度
    const itemHeight = Number.parseInt(
      getComputedStyle(item).getPropertyValue("height")
    );

    // 更新高度,注意,要加上膨胀后的高度。
    topList[index] += (EXPENSION_WIDTH * itemHeight) / MIN_WIDTH + SPACE;

    // 更新元素的宽度
    item.style.width = EXPENSION_WIDTH + "px";
  });

  // 获取父元素,设置高度为最高列的高度。
  const app = document.querySelector(".app");
  app.style.height = Math.max(...topList) + "px";
}

function throttle(cb, time = 200) {
  let timer = null;
  return function () {
    if (timer) return;
    timer = setTimeout(() => {
      cb();
      timer = null;
    }, time);
  };
}
onMounted(() => {
  window.addEventListener("resize", throttle(setPosition));
});
</script>

<style lang="scss" scoped>
.app {
  column-count: 5;
  column-gap: 10px;
  background-color: aquamarine;
  position: relative;

  .item {
    height: fit-content;
    overflow: hidden;
    position: absolute;
    transition: all 0.3s;
    width: 500px;

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    p {
      position: absolute;
      left: 0;
      top: 0;
      background-color: #000;
      color: #fff;
      font-weight: 600;
      font-size: 2vw;
    }
  }
}
</style>
相关推荐
苏卫苏卫苏卫21 分钟前
【Vue】案例——To do list:
开发语言·前端·javascript·vue.js·笔记·list
0509151 小时前
测试基础笔记第四天(html)
前端·笔记·html
聪明的墨菲特i1 小时前
React与Vue:哪个框架更适合入门?
开发语言·前端·javascript·vue.js·react.js
时光少年1 小时前
Android 副屏录制方案
android·前端
拉不动的猪1 小时前
v2升级v3需要兼顾的几个方面
前端·javascript·面试
时光少年1 小时前
Android 局域网NIO案例实践
android·前端
半兽先生2 小时前
VueDOMPurifyHTML 防止 XSS(跨站脚本攻击) 风险
前端·xss
冴羽2 小时前
SvelteKit 最新中文文档教程(20)—— 最佳实践之性能
前端·javascript·svelte
Nuyoah.2 小时前
《Vue3学习手记2》
javascript·vue.js·学习
Jackson__2 小时前
面试官:谈一下在 ts 中你对 any 和 unknow 的理解
前端·typescript