实现瀑布流布局的四种方式--以vue为例(超详细)

vue实现瀑布流的四种方式

背景介绍

当布局内出现元素高度不一的时候,如果采取惯用的flex布局则会出现下列这种存在大量空白的情况,整个布局就留白显得不够紧凑 这个时候可能就希望下面的元素能够顶上去填补空白,也就类似于下面图中的结构那样,这样的布局可以称之为瀑布流布局

方案比较

在开始描述瀑布流布局的具体实现之前,先总结各种方案的特点,可根据项目中的需要找到对应的方法进行使用。其中推荐grid布局以及masonry-layout

方案 JS逻辑 上手难度 自上而下 从左到右 宽度一致 触底加载
多列布局 不需要 支持 不支持 需要 不支持
grid布局 需要 中等 支持 支持 不需要 支持
flex布局 需要 较低 支持 支持 需要 不支持
masonry-layout 需要 中等 支持 支持 不需要 支持

方案样例展示中元素为普通的div元素,如果元素为图片则需将元素切换为图片。图片需设置对应的宽高等比例放大缩小填充的样式(此处不赘述),与高度宽度计算有关的方案还需读取图片的宽高等

1.多列布局

概述

  • 实现方式:多列布局采取的是css样式columns: 4将容器拆分为对应列数,并将元素平均地放入各列当中。同时各元素要设置break-inside: avoid避免各元素被分割
  • 方案特点:该方法能够最为简单地实现瀑布流的效果,但是元素只能从上到下排列
  • 实际应用:在实际项目场景中,往往是将前面的元素优先展示,即从左到右的排列方式。若是需要实现触底加载功能时,除了新增元素外原来的很多元素位置也会发生改变。同时各元素宽度若不一致,会存在横向的留白。该方法比较适用于静态限定数量图片的展示

实现

javascript 复制代码
<template>
    <div class="masonry_1">
      <div class="item" v-for="(item, index) in 20" :key="index">
        {{ index }}
      </div>
    </div>
</template>

<style scoped lang="less">
.masonry_1 {
  width: 100%;
  columns: 4;
  column-gap: 8px;
  .item {
    width: 100%;
    break-inside: avoid;
    margin-bottom: 8px;
    background-color: #ccc;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 20px;

    &:nth-child(n) {
      height: 200px;
    }
    &:nth-child(2n) {
      height: 300px;
    }
    &:nth-child(3n) {
      height: 400px;
    }
  }
}
</style>

2.grid布局

概述

  • 实现方式:利用grid布局将容器切割成足够小的网格,各元素的高度均为该网格的倍数,计算各元素的高度并给该元素分配对应的网格即可
  • 方案特点:自由度很高,不仅能实现竖向的瀑布流,还能实现横向的瀑布流,只需要按照宽高计算占据的网格,便能在grid布局中填充
  • 实际应用:能够适用于大多数的场景,但若元素较多时逐一计算并设置元素样式可能造成卡顿

实现

javascript 复制代码
<template>
    <div class="masonry_2">
      <div class="item" v-for="(item, index) in 20" :key="index">
        {{ index }}
      </div>
    </div>
</template>

<script>
import { defineComponent, ref, onMounted } from "vue";

export default defineComponent({
  name: "MasonryComponent",
  setup() {
    onMounted(() => {
      // grid布局
      const masonry = document.querySelector(".masonry_2");
      const items = masonry.querySelectorAll(".masonry_2 .item");
      items.forEach((item) => {
        // 模拟高度获取,若为图片可获取图片高度
        const height = Math.floor(Math.random() * 200 + 50);
        // 根据元素高度设置元素的需占行数
        const rows = Math.ceil(height / 10);
        item.style.gridRowStart = `span ${rows}`;
      });
    });

    return {};
  },
});
</script>

<style scoped lang="less">
.masonry_2 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  grid-auto-rows: 10px;
  .item {
    background: #ccc;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
</style>
  • 需要设置横向的瀑布流时
javascript 复制代码
<script>
// 计算高度更改为计算宽度即可
const width= Math.floor(Math.random() * 200 + 50);
// 根据元素高度设置元素的需占行数
const columns = Math.ceil(width / 10);
item.style.gridColumnStart = `span ${columns}`;
</script>

<style scoped lang="less">
.masonry_2 {
  display: grid;
  grid-template-rows: repeat(4, 1fr);
  gap: 8px;
  grid-auto-columns: 10px;
  grid-auto-flow: column;
  height: 100vh;
  .item {
    background: #ccc;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
</style>

3.flex布局

概述

  • 实现方式:将数据按照要求分成对应的列数,并按顺序依次放入对应列当中,flex布局中渲染拆分成对应列数的容器,从上到下地排列元素
  • 方案特点:使用熟悉的flex布局,手动控制元素的渲染顺序,采用修改布局构造以及手动拆分数据的方式实现瀑布流
  • 实际应用:虽然能实现瀑布流的效果,但是仅按照各列元素个数进行平分,并不能智能计算各列的高度对空白进行补充,如下图所示可能出现各列高度相差极大的情况。其他方面的支持情况和多列布局相似

实现

javascript 复制代码
<template>
    <div class="masonry_3">
      <div class="column column_1">
        <div class="item" v-for="item in column1" :key="item">
          {{ item }}
        </div>
      </div>
      <div class="column column_2">
        <div class="item" v-for="item in column2" :key="item">
          {{ item }}
        </div>
      </div>
      <div class="column column_3">
        <div class="item" v-for="item in column3" :key="item">
          {{ item }}
        </div>
      </div>
    </div>
</template>

<script>
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "MasonryComponent",
  setup() {
    // felx布局
    const data = ref([
      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    ]);
    let column1 = ref([]), //第一列
      column2 = ref([]), //第二列
      column3 = ref([]); //第三列
    let i = 0;
    while (i < data.value.length) {
      column1.value.push(data.value[i++]);
      if (i < data.value.length) {
        column2.value.push(data.value[i++]);
      }
      if (i < data.value.length) {
        column3.value.push(data.value[i++]);
      }
    }

    return {
      column1,
      column2,
      column3,
    };
  },
});
</script>

<style lang="less" scoped>
.masonry_3 {
  display: flex;
  gap: 8px;
  .column {
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: 8px;
    .item {
      width: 100%;
      background-color: #ccc;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 20px;
    }
  }
  .column_1 {
    .item {
      &:nth-child(n) {
        height: 280px;
      }
      &:nth-child(2n) {
        height: 100px;
      }
      &:nth-child(3n) {
        height: 320px;
      }
    }
  }
  .column_2 {
    .item {
      &:nth-child(n) {
        height: 200px;
      }
      &:nth-child(2n) {
        height: 320px;
      }
      &:nth-child(3n) {
        height: 120px;
      }
    }
  }
  .column_3 {
    .item {
      &:nth-child(n) {
        height: 230px;
      }
      &:nth-child(2n) {
        height: 340px;
      }
      &:nth-child(3n) {
        height: 400px;
      }
    }
  }
}
</style>

4.masonry-layout

概述

  • 实现方式:使用第三方库masonry-layout,内部封装对元素的位置计算实现瀑布流布局
  • 方案特点:无需自己做JS计算,若需延展其他功能需学习相应的官方文档
  • 实际应用:与grid布局同样能支持大多数场景,但需注意的是各元素的宽度应为参数columnWidth的倍数,否则会出现留白

实现

javascript 复制代码
<template>
    <div class="masonry_4">
      <div class="grid-sizer"></div>
      <div class="gutter-sizer"></div>
      <div class="grid-item">1</div>
      <div class="grid-item grid-item-width2">2</div>
      <div class="grid-item">3</div>
      <div class="grid-item">4</div>
      <div class="grid-item">5</div>
      <div class="grid-item">6</div>
      <div class="grid-item">7</div>
      <div class="grid-item">8</div>
      <div class="grid-item">9</div>
    </div>
</template>

<script>
import { defineComponent, reative, onMounted } from "vue";
import Masonry from "masonry-layout";

export default defineComponent({
  name: "MasonryComponent",
  setup() {
    let masonry = reative({})
    onMounted(() => {
      // 第三方库
      masonry = new Masonry(".masonry_4", {
        columnWidth: ".grid-sizer", // 列宽度,未设置时将第一个项的宽度作为列宽度,也支持设置固定宽度
        gutter: ".gutter-sizer", // 添加项元素间的横向间隔,也支持设置固定宽度
        itemSelector: ".grid-item", // 指定将在布局中作为项的子元素
        percentPosition: true, // 将项的位置设为百分比优于设为像素值
        transitionDuration: "0.2s", // 过渡效果持续时间
        stagger: 30, // 项转换位置时为其提供交错效果,用以毫秒为单位的数字
      });
    });
  },
});
</script>

<style scoped lang="less">
.masonry_4 {
  width: 100%;
  height: 100%;
  position: relative;
  .grid-sizer {
    width: calc(25% - 12px);
  }
  .gutter-sizer {
    width: 8px;
  }
  .grid-item {
    width: calc(25% - 12px);
    margin-bottom: 8px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 20px;
    background-color: #ccc;
    &:nth-child(n) {
      height: 200px;
    }
    &:nth-child(2n) {
      height: 300px;
    }
    &:nth-child(3n) {
      height: 400px;
    }
  }
  .grid-item-width2 {
    width: calc(50% - 16px);
    height: 300px;
  }
}
</style>
  • 当渲染的是图片时
javascript 复制代码
import imagesLoaded from 'imagesLoaded'

// 当内部存在图片时,等待图片加载完全再进行排列,否则可能出现项堆叠的情况
imagesLoaded('.masonry_4', () => {
    new Masonry('.masonry_4', {
      columnWidth: '.grid-sizer', // 列宽度,未设置时将第一个项的宽度作为列宽度,也支持设置固定宽度
      gutter: '.gutter-sizer', // 添加项元素间的横向间隔,也支持设置固定宽度
      itemSelector: '.grid-item', // 指定将在布局中作为项的子元素
      percentPosition: true, // 将项的位置设为百分比优于设为像素值
      transitionDuration: '0.2s', // 过渡效果持续时间
      stagger: 30 // 项转换位置时为其提供交错效果,用以毫秒为单位的数字
    })
})
  • 触底加载更多时
javascript 复制代码
// 加载更多元素后重载各元素并重新布局
masonry.reloadItems()
masonry.layout()
相关推荐
香蕉可乐荷包蛋3 小时前
浅入ES5、ES6(ES2015)、ES2023(ES14)版本对比,及使用建议---ES6就够用(个人觉得)
前端·javascript·es6
未来之窗软件服务3 小时前
资源管理器必要性———仙盟创梦IDE
前端·javascript·ide·仙盟创梦ide
西哥写代码4 小时前
基于cornerstone3D的dicom影像浏览器 第十八章 自定义序列自动播放条
前端·javascript·vue
清风细雨_林木木5 小时前
Vue 中生成源码映射文件,配置 map
前端·javascript·vue.js
雪芽蓝域zzs5 小时前
JavaScript splice() 方法
开发语言·javascript·ecmascript
森叶6 小时前
Electron 主进程中使用Worker来创建不同间隔的定时器实现过程
前端·javascript·electron
霸王蟹6 小时前
React 19 中的useRef得到了进一步加强。
前端·javascript·笔记·学习·react.js·ts
霸王蟹6 小时前
React 19版本refs也支持清理函数了。
前端·javascript·笔记·react.js·前端框架·ts
codelxy6 小时前
vue引用cesium,解决“Not allowed to load local resource”报错
javascript·vue.js
程序猿阿伟7 小时前
《社交应用动态表情:RN与Flutter实战解码》
javascript·flutter·react native