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

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>
相关推荐
_AaronWong1 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode1 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户5433081441941 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo1 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
JohnYan1 小时前
工作笔记-CodeBuddy应用探索
javascript·ai编程·aiops
恋猫de小郭2 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木2 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮2 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati2 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉2 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain